Linux进程通信-信号

1.进程

在介绍信号的概念之前,先简单的介绍一下进程的概念:

进程可以理解为一段正在执行的程序,它包括以下三个部分内容:

(1)一段正在执行的程序

(2)与该段程序相关联的全部数据(数据空间,内存,缓冲区)。

(3) 程序计数器(PC).

 

2.信号

可以这么理解信号,信号本身不是消息,它表达的内容才是消息。 Linux信号是软件中断所提供的用于异步处理事件的一种机制。事件可以来自系统外部,或者来自程序或者是内核。

内核处理信号有以下几种方式:

(1)忽略此信号

即不采取任何动作。但有两种信号不能忽略,SIGKILL,SIGSTOP,这是系统杀掉或停止进程的可靠方法。如果一个进程能够忽略此信号,那么将能够不无法被杀掉或停止。这是不允许的。

 

(2)捕获以及处理信号

内核可以暂停当前进程的执行路径,跳到信号处理函数的入口处,执行信号处理,处理完毕后再回到暂停之处。

SIGINT,SIGTERM就是两个可以被捕获的信号。SIGINT对应的CTRL+C,SIGTERM可以在终止前进行必要的处理。

 

(3)执行默认的动作

此动作取决于被送出的信号,默认的动作通常是终止进程。

 

3.信号标识符

每个信号都以一个SIG开头。每个信号会被关联到一个整数值。 信号的编号始于1,而且继续向上线性增长,总共大约有64个信号值,前32种是不可靠信号,后32种是可靠信号。即前32种信号有可能发生信号丢失,而后32种不会。对于不可靠信号而言,当到达多次时,内核只会注册一次。而可靠信号会注册多次,形成信号队列。

 

 

4. 信号的用途

一般说来,信号主要用于以下三个地方:

(1)用户: 用户通过输入CTRL+C,来请求产生内核信号

(2)内核:当内核执行出错时,内核会给进程发送一个信号,如非法内存地址存取。除此之外,内核也通过信号机制来通知事件的发生。

(3)进程: 一个进程可以通过kill函数给另外一个进程发送信号,即信号用于进程间通信。

所以,进程通信就把进程与信号联系起来了。

接收信号的进程与发送信号的进程的所有都必须相同。

 

 

5.进程与信号所用的接口函数

 

int wait(&status);

理解wait函数,必须从以下三个方面进行剖析:

(1) wait函数是用于父进程与子进程之间的同步,当父进程调用wait函数时,会阻塞在那里

(2) 当子进程成功返回时,wait函数返回的是子进程的PID,如果失败返回-1,并将errno变量里面存放ECHILD

(3) 如果status为空的话,则不返回任何信息。否则,status所指向的地址用于返回进程的状态信息。是一个整数值,这个状态信息不仅包括子进程结束时的返回值,即exit(1). 以及是否成功返回。当调用wait函数时,将彻底销毁子进程。

 

int waitpid(pid_t pid,*status, int options);

这个函数与wait函数的功能差不多。

第一个参数:

如果pid>0,那么waitpid等待的是指定进程的pid,如果这个进程结束,那么waitpid返回这个进程的pid.

如果pid=0,那么waitpid等待同一组中的任一进程结束。

如果pid=-1,那么waitpid等待系统中任何一个进程结束。

如果pid<-1,那么waitpid等待进程组号绝对值为pid的任何一个进程结束。

 

waitpid只要检测到一个进程结束时,它便会返回。

 

status同上面的status.

 

options:

 

(1)如果不使用options,将option=0

(2) options可选择的参数有WNOHANG,WUNTRACED

如果使用了WNOHANG,即使子进程没有退出,父进程也不会被阻塞。WUNTRACED是用于调试信息的。

 

int kill(pid_t pid,int sig);

功能: kill函数是向一个指定进程发送信号。

参数:

如果pid>0,那么kill函数会将信号发送给指定的进程

如果pid=0,那么kill函数会将信号发送给同一进程组中的每个进程

如果pid=-1,那么kill函数会将信号发送给调用的进程有权发送的每个进程(权限:一个具有CAP_KILL能力的进程,通常为root所拥有,能够可以发送一个信号给任何进程。若不具备此能力,那么发送进程的用户ID必须等于接收端的用户的ID,也就是一个用户只能发送一个信号给自己的一个进程)。

如果pid<-1,那么kill函数会发送给进程组号的绝对值为pid中的每个进程

返回值: 执行成功后,kill()返回0,只要有信号被送出,此调用就会被视为成功。执行失败后,会返回-1,并且将errno设定为下列一个值:

EINVAL

 signo指定了无效的信号

 

EPERM

  进行调用的进程的权限不足以将一个信号发送给任何请求的进程

 

ESRCH

  pid所指定的进程或进程组不存在,或者是一个僵死进程

 

 

raise(signo)

这个函数是进程给自己发送一个信号 。

相当于kill(getpid(),signo).

 

 

 相关的宏:

WIFEXITED(status) 如果进程调用exit正常退出时,该值为非0

WEXITEDSTATUS(status) 如果WIFEXITED(status)非0时,那么该函数返回exit(3)结束设定的参数值。这里会返回3

WIFSIGNALED(status) 如果子进程是因为接收到信号而退出时,返回非0

WTERMSIG(status) 如果WIFSIGNALED非0时,返回信号代码值。

 

 

下面是与信号相关的例子:

 

 示例1:

 

 /**
wait(&status)与kill(pid_t pid,int sig);

(1)当wait函数调用时,父进程就会被阻塞在那里
(2)wait成功返回的是子进程的PID,如果失败就返回-1
(3)wait函数将结束的子进程的状态存于status中去,这个状态包括子进程的返回值以及是否成功返回

kill
kill函数是将指定信号传递到指定的进程中去
pid>0 将指定的信号sig传递到进程号为pid的进程中去
pid=0 将指定信号sig传递到与目前进程相同的进程组中的所有进程
pid=-1 将信号传递给系统内的所有进程
pid<-1 将信号传递给进程组号的绝对值为pid的所有进程

kill(getpid(),sig);//进程向自身发送信号
**/
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
int childpid;
childpid=fork();
int status;
if(childpid<0){
perror("子进程创建失败");
}

else if(childpid==0){

printf("child proccess/n");

sleep(10);
exit(4);
}


else {
sleep(1);
kill(childpid,SIGABRT);//向子进程发送信号,终止子进程
int pid=wait(&status);//等待子进程结束,收集子进程信息
printf("%d/n",pid);
if(WIFSIGNALED(status)){//如果是被信号中断
printf("child proccess receive singal %d/n",WTERMSIG(status));//返回中断的信号代码


else {
if(WIFEXITED(status)){
printf("子进程是正常退出的,退出码为%d/n",WEXITSTATUS(status));
}
}
}
}

 

 

执行结果为:

child proccess

3719

child proccess receive signal 6

 

分析: 父进程在睡眠1秒后,发送SIGABRT信号,然后在wait处阻塞,等待子进程终止返回。只要有任何一个子进程退出时,父进程就会在wait处返回。

 

示例2:

 

/**
wait(&status)与waitpid(pid_t pid,*status,int options)

(1)当父进程调用wait函数时,父进程被阻塞在那里
(2)wait函数返回值是子进程的PID,如果失败,则返回-1,并将ECHILD存入到errno
(3)wait函数将子进程结束时的状态存入到status中去,将子进程彻底销毁,销毁进程表项

waitpid与wait的功能是相同的,不过多了几个参数
pid

(1)pid>0 只等待pid的进程结束,如果其它进程结束时,waitpid还会被阻塞,直到指定pid的进程结束为止
(2)pid=-1等待任何一个子进程退出,此时wait与waitpid的功能是一样的
(3)pid=0等待同一个进程组中的任何一个进程退出
(4)pid<-1 等待一个指定进程组中的任何进程退出,这个进程组的pid等于pid的绝对值


options
(1)如果不使用options,将options设置为0
(2)options可取参数 WNOHANG,WUNTRACED
如果使用了WNOHANG,即使子进程没有退出,父进程也不会阻塞,依旧执行下去
而WUTRACED是用于跟踪调试的信息
**/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
int childpid1;
int childpid2;
int pr=-1;
int status;
childpid2=fork();
if(childpid2==0){

sleep(1);
exit(10);

}

childpid1=fork();
if(childpid1==0){
sleep(5);

//kill(getpid()SIGABRT);//向自己发送信号,终止自己
raise(SIGABRT);
exit(4); 

}

else {
do{
pr=waitpid(childpid1,&status,WNOHANG);//WNOHANG这个参数将不再阻塞父进程,父进程继续执行下去
printf("pr=%d",pr);
if(pr==0){
sleep(1);//没有收集到子进程
}
printf("等待子进程返回/n");
}while(pr==0);

if(WIFEXITED(status)){

printf("子进程已经返回,返回值为:%d/n",WEXITSTATUS(status));
}

else {
if(WIFSIGNALED(status)){
printf("子进程发送信号量:%d/n",WTERMSIG(status));
}

}

}

}

 

 

 

执行结果为:

pr=0等待子进程返回

pr=0 等待子进程返回

pr=0等待子进程返回

pr=0等待子进程返回

pr=0等待子进程返回

pr=15443等待子进程返回

子进程发送的信号量为:6

 

分析: 该程序中有两个子进程,一个是正常退出,另一个是用raise函数向自己发送信号,终止自己。waitpid只等待第一个子进程结束。

即使第二个子进程先结束也不理。

waitpid函数中第三个参数是WNOHANG,即子进程没有终止时,也不会阻塞父进程,但此时waitpid的返回值会为0.

可以用 man waitpid看看它的使用及返回值。

 

 

这篇文章中简单的介绍了进程与信号的使用情况。总之,记住两点:

(1) 进程会阻塞在wait函数那里,waitpid 的WNOHANG可以让父进程不阻塞。

(2) 进程用kill函数可以向另外一个进程发送信号。raise向自身发送信号。

 

wait(&status)=waitpid(-1,&status,0);

kill(getpid(),signo)=raise(signo);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

阅读更多
文章标签: linux kill signal c
个人分类: LINUX
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭