Linux编程-进程

本文最后更新于:1 年前

  • 进程是资源分配的基本单位
  • 进程有独立的地址空间

pid_t getpid(void)//获取进程号

pid_t getppid(void)//获取父进程号

pid_t getpgid(pid_t pid)//获取pid所属组进程号

退出进程

exit(int status)(stdlib.h)

_exit(int status)(unistd.h)

exit会刷新IO缓冲,输出缓冲区里的内容,_exit()会直接退出,缓冲区里的内容被丢弃。

孤儿进程

父进程结束,子进程还在运行。被init进程接管回收资源。所以孤儿进程不会有危害。

僵尸进程

进程终止时,会释放用户区数据,但是内核区PCB需要父进程释放,如果父进程不调用wait() /waitpid(),该进程号就会一直占用,PCB一直存在内核,变为僵尸进程。(进程号会被一直占用,系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程)

僵尸进程不能被kill -9杀死。

在内核源码中有如下的代码:

1
2
3
4
5
6
7
8
do_exit->exit_notify->
state = EXIT_ZOMBIE
if (tsk->exit_signal == -1 &&
(likely(tsk->ptrace == 0) ||
unlikely(tsk->parent->signal->flags & SIGNAL_GROUP_EXIT)))

state = EXIT_DEAD;
tsk->exit_state = state;

说明如果定义了子进程退出时向父进程发送信号,则设置进程状态为EXIT_ZOMBIE,否则为EXIT_DEAD。而子进程退出时一定会向父进程发送信号,所以进程的状态为EXIT_ZOMBIE,如果此时父进程调用wait等待子进程结束的话,由 do_wait->wait_task_zombie函数可以将进程的状态设置为EXIT_DEAD,并且释放进程的内核堆栈资源,最后由 put_task_struct将其task_struct结构体释放掉。否则子进程会变成僵尸进程。

解决方案

1)此时可以杀死父进程,其产生的僵尸进程会被init进程接管,调用wait() 回收进程表中的资源。

2) wait()/waitpid()得到子进程退出状态同时清除该进程的相关资源,但一次只能处理一个子进程。

3)通过信号机制:子进程退出时,向父进程发送 SIGCHILD 信号,父进程处理 SIGCHILD 信号,在信号处理函数中调用 wait 进行处理僵尸进程。

pid_t wait(int *wstatus) //阻塞 直到一个子进程退出,或者收到一个不能被忽略的信号才被唤醒,往下执行*wstatus: 进程退出时的状态信息,传入的是一个int类型的地址,传出参数。成功返回被回收的进程id,失败:-1(所有子进程都结束)

pid_t wait_pid(pid_t pid,int * wstatus,int options) 可以设置不阻塞与指定等待哪个子进程。

​ //pid>0 回收某个子进程的pid =0: 回收当前进程组的所有子进程 =-1:回收所有子进程,相当wait().<-1: 回收某个进程组的组id的绝对值。

​ //>0 返回子进程id =0 表示还有子进程没有退出 -1 都退出了

可以通过将 options 设置为常量 WNOHANG、WUNTRACED 和 WCONTINUED,0: 阻塞

的各种组合来修改默认行为:

WNOHANG: 如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为0)。 默认的行为是挂起调用进程,直到有子进程终止 。在等待子进程终止的同时,如果还想做些有用的工作,这个选项会有用。

WUNTRACED: 挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止 。返回的PID 为导致返回的已终止或被停止子进程的 PID。默认的行为是只返回已终止的子进程。当你想要检査已终止和被停止的子进程时,这个选项会有用。

WCONTINUED: 挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一 个被停止的进程收到 SIGCONT 信号重新开始执行。

​ 可以用或运算把这些选项组合起来 。例如:WNOHANG | WUNTRACED: 立即返回,如果等待集合中的子进程都没有被停止或终止,则返回值为0: 如果有一个停止或终止,则返回值为该子进程的 PID。

4)fork两次:

原理是将进程成为孤儿进程,从而其的父进程变为 init 进程,通过 init 进程处理僵尸进程。具体操作为:父进程一次 fork() 后产生一个子进程随后立即执行 wait(NULL) 来等待子进程结束,然后子进程 fork() 后产生孙子进程随后立即exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行。这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给Init进程托管。于是父进程与孙子进程无继承关系了,它们的父进程均为Init,Init进程在其子进程结束时会自动收尸,这样也就不会产生僵死进程了