进程的控制

进程的创建

frok函数添加进程

进程=内核的相关管理数据结构(task_struct +mm_struct+页表)+代码数据

特性:
  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
fork可能调用失败
  • 因为一个系统的进程一定有一个上限,及用户线程到达上限
  • 系统资源已满。
fork的返回值
  • 子进程返回0
  • 父进程返回子进程pid。

为了方便父进程对子进程标识,方便管理。

进程的终止

终止是在做什么?

释放曾经的代码和数据结构所占据的空间,释放内核数据结构(僵尸)task_sturct 停滞状态

进程退出码

return返回退出码(可返回自定义退出码)

退出码分为:

  • 自定义退出码
  • 系统默认退出码

都交由bash处理,但只存在最新的一个子进程的退出码。

父进程bash为什么要知道子进程的退出码(让用户知道)?

因为要知道子进程的退出情况,如果用问题就终止。

进程终止的3中情况。

跑完

1.代码正确,结果正确

2.代码跑完,结果不正确

都可以通过进程的退出码决定。

非跑完

3.代码异常终止

vs 编程运行时,操作系统发现你的进程做了不该做的事情,os杀掉进程。

异常本质:因为进程收到了os的信号,同时,一旦出现异常,退出码无意义。

一个进程退出,我们可以通过两个数字知道原因:退出码,退出信号(task_struct结束保留等待父进程),且父进程必须知道。

如何终止

main函数return,表示进程终止(非main,函数结束)

代码调用exit函数,表示进程终止(c语言库函数,刷新缓存区)

_exit(系统调用,也是exit的底层调用)

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

1. 执行用户通过 atexit或on_exit定义的清理函数。

2. 关闭所有打开的流,所有的缓存数据均被写入

3. 调用_exit

进程等待

结论:任何子进程,在退出情况下,一般必须要被父进程等待,否则僵尸,原因:

父进程通过等待,解决子进程的退出僵尸问题,回收系统资源(必要性)。

获取子进程的退出信息,知道子进程时因什么原因退出(充要性)。

如何等待(释放僵尸)

wait方法

#include

#include

pid_t wait(int*status);

返回值:

成功返回被等待进程pid,失败返回-1。参数:

输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

int *status;

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

当正常返回的时候waitpid返回收集到的子进程的进程ID;

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

  1. pid:

Pid=-1,等待任一个子进程。与wait等效。

Pid>0.等待其进程ID与pid相等的子进程。

  1. status:WIFEXITED(status):

若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码) options:

不休眠等待

WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞(永久休眠)。
  • 如果不存在该子进程,则立即出错返回。

等待父进程的一个子进程结束

子进程本身是一个软件,父进程本质实在等待某种条件就绪(阻塞等待)。

其中status变量是一个输出变量,他保存的是32位的退出码与退出信号的二进制合并。

WNOHANG等待方式:

#include <stdio.h> 
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
 pid_t pid;
 
 pid = fork();
 if(pid < 0){
  printf("%s fork error\n",__FUNCTION__); return 1;
 }
 else if( pid == 0 ){ 
     //child 
     printf("child is run, pid is : %d\n",getpid()); sleep(5); exit(1);
 }
  else{
    int status = 0; 
    pid_t ret = 0; 
    do {
        ret = waitpid(-1, &status, WNOHANG);
        //非阻塞式等待 
        if( ret == 0 ){
          printf("child is running\n");
       }
     sleep(1);
   }while(ret == 0);
  if( WIFEXITED(status) && ret == pid )
   { 
    printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status)); }else{ printf("wait child failed, return.\n"); return 1;
   }
  }
 return 0;
}

进程程序替换

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

替换原理

进程之间的替换是将代码与数据(目标数据代码)与源数据交换(在执行过程中掩盖源数据代码),从而实现进程之间的替换,其中堆栈中的数据会重新刷新。

常用法:

通过子进程执行该进程,其中子进程会执行内存上的一块新空间(写时拷贝),不影响父进程的使用。

ps:exec是程序替换函数,本身并不创建进程

常见函数替换:

#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值

命名理解

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

使用:


#include <unistd.h>
int main()
{
 char *const argv[] = {"ps", "-ef", NULL}; char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
 execl("/bin/ps", "ps", "-ef", NULL);
 // 带p的,可以使用环境变量PATH,无需写全路径 execlp("ps", "ps", "-ef", NULL);
 // 带e的,需要自己组装环境变量
 execle("ps", "ps", "-ef", NULL, envp);
 execv("/bin/ps", argv);
 
 // 带p的,可以使用环境变量PATH,无需写全路径 execvp("ps", argv);
 // 带e的,需要自己组装环境变量 execve("/bin/ps", argv, envp);
 exit(0);
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
下图exec函数族 一个完整的例子: 

  • 9
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值