【Linux】进程创建(fork函数)及其等待与退出

进程创建

进程是把该程序运行起来加载到内存,受OS控制,建立PCB,OS把PCB管理好就是把进程管理好。
以fork为例:

fork函数

在Linux中fork函数是非常重要的函数,会创建子进程,原进程是父进程。
子进程返回0,父进程返回子进程id。
进程调用fork,当控制转移到内核中的fork代码后,内核做以下动作:

  • 分配新的内存块和内核数据给子进程;

  • 将父进程部分数据结构内容拷贝至子进程;

  • 添加子进程到系统进程列表当中;

  • fork返回,开始调度器调度;

当一个进程调度用fork之后,就是两个二进制代码相同的进程。而且他们都运行到相同的地方。但每次进程都将可以开始他们自己的进程。

int main(){pid_t pid;printf("Before: pid is %d\n",getpid());if((pid = fork()) == -1)perror("fork()"),exit(1);printf("After:pid is %d,fork return %d\n",getpid(),pid);sleep(1);return 0;}

这里输出3行,一行before,两行After。进程5643先打印before消息,然后又打印after,另一个after消息由5644打印。但5644没有打印after,证明:
fork之后父进程独立执行,fork之后,父子两个执行流分别执行。注意:fork之后,谁先执行完全由调度器决定。
fork之后,父子进程代码共享,数据以写时拷贝的方式各自私有一份。

fork常规用法
  • 父子进程执行不同的代码;

  • 一个进程要执行一个不同的程序。

fork调用失败的原因
  • 系统中有太多的进程

  • 实际用户的进程数超过了限制

进程终止

进程退出场景
  • 代码运行完毕,结果正确

  • 代码运行完毕,结果不正确

  • 代码异常终止

进程退出的方法
  • 从main中返回

  • 调用exit

  • _exit

异常退出:ctrl+从,信号终止

_exit:终止进程,数据清空;
exit:终止进程,数据不清空;
return退出:从main函数返回

进程等待

进程等待的必要性

子进程退出,父进程不管不顾,就可能造成“僵尸进程”的问题,进而造成内存泄露;
进程一旦变成僵尸状态,那就刀枪不入,kill -9也无能为力;
父进程派给子进程的任务完成如何,我们需要知道;
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

进程等待的方法
wait方法

成功返回被等待进程pid,失败返回-1;

waitpid方法

pid_t waitpid(pid_t pid,int *status,int options);
返回值:正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用出错,返回-1,这时errno会被设置成相应的值以指示错误所在。
pid:
pid = -1,等待任一个子进程,与wait等效;
pid >0,等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status):若为正常终止子进程返回的状态,则为真。
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。
options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立刻返回并释放资源,获得子进程信息;
如果在任意时刻调用wait/waitpid,子进程存在并还没有退出时,进程可能阻塞;
若不存在子进程,立即出错返回。

获取子进程的status

status是一个参数,是一个输出型参数,由操作系统填充,wait和waitpid都存在;
若参数为NULL,表示不关心子进程的退出状态,否则,OS根据该参数,将子进程的退出信息返回给父进程;
status可以当做位图来看:只研究低16比特位

正常终止:8-15位是退出状态;
被信号杀死:看低8位,查看终止信号。
测试代码:(正常终止与信号kill)

#include <stdio.h>#include <stdlib.h>#include <sys/wait.h>#include <error.h> int main() {     pid_t id = fork();      if(id < 0)       {          perror("fork");          exit(1);       }      if(id == 0)      {          printf("I am child...\n");          sleep(20);          exit(10);      }      else      {          int st;          int ret = wait(&st);          if(ret > 0 && (st & 0x7F) == 0)          {               //正常终止              printf("child exit code:%d\n",(st >> 8)&0x7F);          }          else if(ret > 0)          {              //异常退出              printf("sig code:%d\n",st&0x7F);          }      }  }

正常终止,等20秒后

异常终止:

示例:

int status;

pid_t t = fork();
if(t){
    waitpid(t, &status, 0);
}else{
    system("vi temp.txt");
    exit(0);
}
//父进程和子进程均执行完毕后继续执行下去
分析过程:
if 和 else 还是选择分支。 
主要的原因是,fork() 函数调用一次,返回两次。两次返回的区别是:子进程的返回值是0,父进程返回值为新子进程的进程ID。返回后,父进程执行waitpid(t, &status, 0)等待子进程结束,而子进程进入另一个分支执行system("vi temp.txt");exit(0);,父子间并不冲突,可以形容这段代码父进程与子进程都执行了一次判断
(0)

相关推荐