进程与信号这部分内容在linux 网络编程经常用到,如服务器守护进程,在此先概括一下基本知识。
一、进程
1.创建进程
#include<unistd.h>
pid_t fork(void);
父进程中,函数fork返回子进程的进程号,应该是一个正整数;子进程中,函数fork返回0,调用失败,返回-1。
2.创建新进程的必要处
进程需要执行一个新的任务,而它自己同时执行另一个任务;例如,在网络程序中,父进程完成接收客户机连接的任务,
而子进程完成处理客户机请求的任务。
一个进程需要执行另一个程序,它首先创建一个子进程,然后将子进程的正文改变为所有执行的程序。Shell进程就是以
这种方式执行用户输入的命令的。在这种场合下,子进程需要调用exec来改变正文部分。该系统调用有6种不同的形式,不
同的是参数。int execve,execl,execlp,execle,execv,execvp.
3.用户标识号
两个难以区分的用户号:实际用户标识号与有效用户标识号。前者标识运行进程的用户,后者用于给新创建的文件赋所有权,
检查文件的存取权限和检查通过系统调用kill向进程发送信号的许可权限。
二、信号
1.捕获信号
进程通过设置信号的处理函数来捕获一个信号,sigaction设置信号的处理函数。
#include<signal.h>
int sigaction(int signum,const struct sigaction*act,struct sigaction*oldact);
signum是除了SIGKILL和SIGSTOP外的任何信号,成功执行返回0,否则-1.
struct sigaction{
void (*sa_handler)(int);//指定信号的动作,默认时为SIG_DFL,忽略为SIG_IGN,自定义时设置为用户的信号处理函数名
sigset_t sa_mask;//哪些信号将被屏蔽
int sa_flags;//
void (*sa_restore)(void);//
};
处理一个信号的过程中,可能接受新的信号,可以选择性的屏蔽某些信号的处理,linux提供的函数有:
int sigprocmask(int how,const sigset_t*set,sigset_t*oldset);//改变当前被屏蔽的信号集
int sigpending(sigset_t *set);//检查当前未处理的信号,将他们的屏蔽码存储在set中
int sigsuspend(const sigset_t*mask);//暂时将进程的屏蔽信号集替换为参数mask的值,然后暂停进程,等待信号,函数返回之前,回复原来的屏蔽信号集。
提供的控制信号集的函数:
int sigemptyset(sigset_t *set);//清空信号集
int sigfillset(sigset_t *set);//填满信号集
int sigaddset(sigset_t *set,int signum);//向信号集中添加一个信号
int sigdelset(sigset_t *set,int signum);//删一个
int sigismember(const sigset_t *set,int signum);//是否属于信号集
向进程发信号函数:int kill(pid_t pid,int sig);//切莫望文生义
三、进程终止exit函数
linux系统中的进程执行exit来终止,系统释放进程的资源,删除进程的上下文,但是保留它的进程表项,向它的父进程发送信号SIGCHLD,此时进程处于Zombie状态。如何处理进程的死亡问题呢?
看一个例子:
int main()
{ int i;
for(i=0;i<5;i++)
if(fork()==0)
exit(0);
for(;;){}
}
程序运行时,执行ps x检查,可知:
上面标志“Z+”的就是僵尸进程(zombie)。不信可以通过 cat /proc/21191/status查看,里面有State项。
如何处理SIGCHLD信号来消除僵尸子进程呢?
1.忽略SIGCHLD信号
struct sigaction act;
act.sa_handler=SIG_IGN;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGCHLD,&act,NULL);
当进程忽略该信号之后,内核将清除子进程的进程表项。这种做法近限于linux,有些unix不清除。
2.调用函数wait或waitpid等待子进程终止然后清除进程表项。
pid_t wait(int *statloc);//等待任何一个子进程终止;返回终止的子进程号,参数存储子进程的终止状态。
pid_t waitpid(pid_t pid,int *statloc,int option);//等待特定的子进程终止,option=WNOHANG时,如果没有终止的子进程,函数立即返回,不阻塞。
为了处理所有终止的子进程,可以循环的调用这两个函数:
while((pid=wait(&status))>0){
printf("child %d died,exit code %d\n",pid,status);
}
这种处理方法使得父进程在所有子进程终止之前,不能再处理其他任务,所有一般在父进程没有其他任务时才使用这种方法。
3.捕获SIGCHLD信号——最理想的解决方案
在自己的函数中处理这个信号,如下;
void sigchld_handler(int signo)
{ pid_t pid;int status;
while((pid=waitpid(-1,&status,WNOHANG))>0){}//不能用wait
return;
}
void main(){
...
struct sigaction act;
act.sa_handler=sigchld_handler;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
if(sigaction(SIGCHLD,&act,NULL)<0){
perror("sigaction error\n");
exit(1);
}
...
}
不影响父进程处理其他任务。
4.调用fork两次
父进程调用fork,然后子进程中再次调用函数fork,而子进程调用函数exit终止。
pid=fork();
if(pid==0){
for(i=0;i<5;i++)
if(fork()==0){printf("chile %d \n",getpid());
sleep(1);
exit(0);
}//if
exit(0);
}//if
for(;;);