下面是有关进程基础API的相关介绍,希望对你有所帮助!
目录
1. 僵尸进程与孤儿进程
1.1 孤儿进程
父进程先于子进程结束,此时子进程变成了一个“孤儿”我们把这种进程就称为孤儿进程。
在 Linux 系统当中,所有的孤儿进程都自动成为 init 进程的子进程
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
// 创建子进程
switch (fork())
{
case -1:
perror("fork error");
exit(-1);
case 0:
// 子进程
printf("子进程<%d>被创建, 父进程<%d>\n", getpid(), getppid());
sleep(3); // 休眠 3 秒钟等父进程结束
printf("父进程<%d>\n", getppid()); // 再次获取父进程 pid
_exit(0);
default:
// 父进程
break;
}
sleep(1); // 父进程休眠休眠 1 秒,保证子进程能够打印出第一个 printf()
printf("父进程结束!\n");
exit(0);
}
代码运行结果
1.2 僵尸进程
如果子进程先于父进程退出,同时父进程太忙了,无瑕回收子进程的内存资源,那么此时子进程就变成了一个 僵尸进程,僵尸一词指的是子进程结束后其父进程并没有来得及立马给它“收尸”,子进程处于“曝尸荒野”的状态
回收进程有以下几种情况:
- 如果父进程调用wait()为子进程收尸"后,僵尸进程就会被内核彻底删除。
- 如果父进程并没有调用wait()函数然后就退出了,那么此时init进程将会接管它的子进程并自动调用wait(),故而从系统中移除僵尸进程
- 如果父进程创建了某一子进程,子进程已经结束,而父进程还在正常运行,但父进程并未调用wait()回收子进程,此时子进程变成一个僵尸进程
如果系统中存在大量的僵尸进程,它们势必会填满内核进程表,从而阻碍新进程的创建,所以,在我们的一个程序设计中,一定要监视子进程的状态变化,如果子进程终止了,要调用wait()将其回收,避免僵尸进程。
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
// 创建子进程
switch (fork())
{
case -1:
perror("fork error");
exit(-1);
case 0:
// 子进程
printf("子进程<%d>父进程<%d>\n", getpid(), getppid());
sleep(1); // 休眠 1秒钟
printf("子进程结束!\n");
_exit(0);
default:
// 父进程
break;
}
while(1)
sleep(1);
exit(0);
}
代码运行结果
2. 监视子进程
在很多应用程序的设计中,父进程需要知道子进程于何时被终止,并且需要知道子进程的终止状态信息,是正常终止、还是异常终止亦或者被信号终止等,意味着父进程会对子进程进行监视,同时还需要回收僵尸进程的资源
2.1 wait()
系统调用 wait() 可以等待进程的任一子进程终止,同时获取子进程的终止状态信息
//函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
函数参数和返回值含义如下:
status:参数 status 用于存放子进程终止时的状态信息,参数 status 可以为 NULL,表示不接收子进程 终止时的状态信息。
返回值:若成功则返回终止的子进程对应的进程号;失败则返回-1。
参数 status 不为 NULL 的情况下,则 wait() 会将子进程的终止时的状态信息存储在它指向的 int 变量中,可以通过以下宏来检查 status 参数:
示例代码
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
int status;
pid_t pid;
int i;
int ret;
for(i = 1; i<4; i++)
{
pid = fork();
switch(pid)
{
case -1:
perror("fork error");
exit(-1);
case 0:
// 子进程
printf("子进程<%d>被创建\n", getpid());
sleep(i);
_exit(i);
default:
// 父进程
break;
}
}
sleep(1);
for(i = 1; i<4; i++)
{
ret = wait(&status);
if(ret == -1)
{
if(ECHILD == errno)
{
printf("没有等待回收的进程\n");
exit(0);
}
else
{
perror("wait error");
exit(-1);
}
}
printf("回收的子进程id: %d,回收状态:%d\n", ret, WIFSIGNALED(status));
}
exit(0);
}
代码运行结果
2.2 waitpid()
使用 wait()系统调用存在着一些限制,这些限制包括如下:
如果父进程创建了多个子进程,使用 wait()将无法等待某个特定的子进程的完成,只能按照顺序等待下一个子进程的终止,一个一个来、谁先终止就先处理谁。
如果子进程没有终止,正在运行,那么 wait() 总是保持阻塞,有时我们希望执行非阻塞等待,而waitpid()函数则可以突破这些限制。
//函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
status: 与 wait()函数的 status 参数意义相同
返回值: 返回值与 wait()函数的返回值意义基本相同
3. 执行新程序
当子进程的工作不再是运行父进程的代码段,而是运行另一个新程序的代码,那么这个时候子进程可以通过 exec 函数来实现运行另一个新的程序。
exec族函数
exec 族函数包括多个不同的函数,这些函数命名都以 exec 为前缀,这些库函数都是基于系统调用 execve() 而实现的,虽然参数各异、但功能相同, 包括: execl()、 execlp()、 execle()、 execv()、 execvp()、 execvpe()
示例代码
// child.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
// 倒数 n 秒
for(int i=atoi(argv[1]); i>0; i--)
{
printf("%d\n", i);
sleep(1);
}
// 程序退出,返回 n
exit(atoi(argv[1]));
}
// father.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
// 子进程
if(fork() == 0)
{
printf("加载新程序之前的代码\n");
// 加载新程序,并传递参数3
execl("./child", "./child", "3", NULL);
printf("加载新程序之后的代码\n");
}
// 父进程
else
{
// 等待子进程的退出
int status;
int ret = waitpid(-1, &status, 0);
if(ret > 0)
{
if(WIFEXITED(status))
printf("[%d]: 子进程[%d]的退出值是:%d\n",getpid(), ret, WEXITSTATUS(status));
}
else
{
printf("暂无僵尸子进程\n");
}
}
}
代码运行结果
- 注意:子进程中加载新程序之后的代码无法运行,因为已经被覆盖了。
4. 守护进程
守护进程(Daemon) 也称为精灵进程,是运行在后台的一种特殊进程,它独立于控制终端并且周期性 地执行某种任务。
输入终端命令 ps -aux ,TTY 一栏是 ?表示该进程没有控制终端,也就是守护进程,其中 COMMAND 一栏使用中括号[]括 起来的表示内核线程,这些线程是在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常 采用 k 开头的名字,表示 Kernel
如果喜欢请不吝给予三连支持!