进程控制
fork()函数
#include<unistd.h>
pid_t fork(void);
返回值: 子进程返回0 父进程返回子进程的pid
fork之前父进程独立运行,fork之后,父子进程分别执行
——》 fork之后会有两个进程,进程具有独立性,所以父进程和子进程的代码和数据都是独立的。
(fork之后,父子进程谁先执行完全由调度器来决定)
进程调用fork,当控制转移到内核中的fork代码后,内核会:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表中
- fork返回,开始调度器调度
写实拷贝
通常,父子代码共享,当不进行写入时,数据也是共享的,但是当任意一方试图写入数据,便以写实拷贝的方式各自一份副本。
fork调用失败的原因
- 系统中有太多的进程
- 实际用户的进程数超过了限制
进程终止
进程退出的几种情况
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
c/c++中main函数最后一般都会有一个 return 0 ,那为什么是0 不能是别的值呢?而这个return 又是return给谁呢?
return是进程的退出码,表征进程的退出信息,是给父进程读取的,如果return 0表示代码运行完毕,并且结果正确,如果是非0说明结果不正确,一般而言,不同错误的退出码可以自定义,表征不同的错误信息,让父进程读取。 在bash中会保存最近一次的进程执行完毕的退出码,通过命令
echo $? 来查看
进程常见的退出方法:
- 从main返回
- 调用exit
- 调用_exit
异常退出:
- ctrl + c
_exit函数
#include<unistd.h>
void _exit(int status);
参数: status定义了进程的终止状态,父进程通过wait来获取该值。
- 注意:虽然status是int,但是仅有低8位可以被父进程所用,所以当_exit(-1)时,在终端执行 echo $? 返回值是255
exit函数
#include<unistd.h>
void exit(int status);
exit和_exit很相似,但是exit还会多做一些工作:
- 执行用户通过atexit 或 on_exit定义的清理函数
- 关闭所有打开的流,刷新缓冲区
- 调用_exit()
进程等待
进程等待的必要性:
- 子进程退出,父进程如果不管不顾,不去回收子进程的资源,就会造成僵尸进程,从而导致内存泄漏。
- 进程一旦进入僵尸状态,就无法被杀死,kill -9也无能为力
- 父进程需要知道子进程的任务完成如何,运行是否完成,是否有异常,结果是否正确?
- 父进程通过进程等待的方式,回收子进程资源,获取子进程信息
进程等待的方法
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status)
返回值:成功返回返回被等待进程pid,失败返回-1
参数:输出型参数,获取子进程退出状态,不关心则可以设置称为NULL。
waitpid()方法
pid_t waitpid(pid_t pid, int *status,int options);
返回值
当正常返回的时候waitpid返回收集到子进程的进程ID
如果设置了选项WHOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
如果调用中出错,则返回-1,这是errno会被设置成相应的值以指示错误所在
参数:
pid:
pid = -1,等待任意一个子进程,与wait等效
pid>0 ,等待其进程ID与pid相等的子进程
status:
WIFEXITED(status):若正常终止子进程返回的状态,则为真。(查看进程是否正常退出)
WEXITSTATUS(status): 若WIFEXITED非0,则提取子进程的退出码。
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回该子进程的ID。
- 如果子进程已经退出,则调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程的退出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立刻出错返回。
获取子进程status
- wait和waitpid都有一个status参数,该参数作为输出型参数,由操作系统填充。
- 如果传的值是NULL,表示不关心子进程的退出状态信息。
- 否则操作系统会根据该参数将子进程的信息返回给父进程
- status有点类似位图,具体细节见下图。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
while (1)
{
printf("我是子进程, 我正在运行...Pid: %d\n", getpid());
sleep(5);
break;
}
exit(0);
}
else
{
int status = 0;
printf("我是父进程:pid:%d, 我准备电脑等待子进程啦\n", getpid());
pid_t ret = waitpid(id, &status, 0);
if (ret > 0)
{
if (WIFEXITED(status))
{
printf("子进程是正常退出的,退出码: %d\n", WEXITSTATUS(status));
}
else
{
printf("子进程异常退出,%d", status & 0X7F);
}
}
else
{
printf("出错");
}
}
return 0;
}
上述我们用的都是进程阻塞等待,阻塞等待本质就是父进程由R状态进入S状态,从运行队列退出,进入等待队列,等待子进程的退出。
而在waitpid的参数option中我们可以设置为非阻塞等待,非阻塞等待就是进程会检查子进程是否运行结束,如果暂时没有运行结束,那么父进程不会等待子进程,不会把自己变为阻塞状态,而是继续去干自己的事情。我们可以多次调用非阻塞等待,进行轮询检测