一、wait()和waitpid()的作用
首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。
二、僵尸进程的作用
一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。
任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。
三、使用wait()和waitpid()处理僵尸进程三种方式
1、通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。
2、父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
3、如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
void wait4children(int signo) {
int status;
wait(&status);
}
int main() {
int i;
pid_t pid;
signal(SIGCHLD, wait4children);
for(i=0; i<100; i++) {
pid = fork();
if(pid == 0)
break;
}
if(pid>0) {
printf("press Enter to exit...");
getchar();
}
return 0;
}
但是通过运行程序发现还是会有僵尸进程,而且每次僵尸进程的数量都不定。这是为什么呢?其实主要是因为Linux的信号机制是不排队的,假如在某一时间段多个子进程退出后都会发出SIGCHLD信号,但父进程来不及一个一个地响应,所以最后父进程实际上只执行了一次信号处理函数。但执行一次信号处理函数只等待一个子进程退出,所以最后会有一些子进程依然是僵尸进程。
虽然这样但是有一点是明了的,就是收到SIGCHLD必然有子进程退出,而我们可以在信号处理函数里循环调用waitpid函数来等待所有的退出的子进程。至于为什么不用wait,主要原因是在wait在清理完所有僵尸进程后再次等待会阻塞。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
void wait4children(int signo) {
int status;
while(waitpid(-1, &status, WNOHANG) > 0);
}
int main() {
int i;
pid_t pid;
signal(SIGCHLD, wait4children);
for(i=0; i<100; i++) {
pid = fork();
if(pid == 0)
break;
}
if(pid>0) {
printf("press Enter to exit...");
getchar();
}
return 0;
}
四、waitpid使用WNOHANG的作用
在父进程不打算阻塞等待子进程返回时,可以这样使用。父进程可以定期轮询子进程的状态。
具体实现:
while(waitpid(-1, &status, WNOHANG) > 0);
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结
束。
WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等
待。
使用while来轮询
五、信号处理函数
信号处理函数必须是可重入函数。
不可重入函数有以下特点:
①使用了静态数据结构,那么不可重入,因此继续执行时,结构可能被修改②调用了malloc函数。free函数,因为会访问共享数据结构
③使用了标准IO函数,标准IO函数会维护共享的缓冲区
malloc和free函数是一个线程安全函数,调用时将陷入内核,内部维护一个空闲区链表来满足堆内存的申请获取和释放回收。因此malloc函数是不可重入的,因为其在执行过程中如果被信号打断,之后继续执行时,可能内部的空闲区链表已经发生变化。
六、read和recv函数区别
recv和send函数提供了和read和write差不多的功能.但是他们提供了第四个参数来控制读写操作。
int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)(不太常使用)
recv函数主要用的flags选项就是MSG_PEEK
MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容。这样下次读的时候,仍然是相同的内容。一般在有多个进程读写数据时能够使用这个标志。
参考链接:https://www.cnblogs.com/lancidie/archive/2013/04/12/3016413.html
参考链接:https://blog.csdn.net/qq_33369979/article/details/110674929
参考链接:https://blog.csdn.net/ChenAiZiTeng/article/details/25611941