赵老师笔记----信号
一、进程间通信(IPC:Inter Process Communcation)
借助内核实现进程间的信息交互
1.进程间通信功能
①数据传输:一个进程需要将它的数据发送给另一个进程。
②资源共享:多个进程之间共享同样的资源。 ③通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。 ④进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。
2.进程间通信的方式
①信号(signal):异步通信方式
②无名管道、有名管道(pipe):实现不同进程间信息的交换
以上继承unix系统的IPC机制
③消息队列(message queue):进程间通信最容易的方式,效率最低
④共享内存(share memory):进程间通信最快速的方式,效率最高,而且可批量
数据交换
⑤信号量(samaphore):实现资源的保护机制,包括同步和互斥
以上三种继承于SYSTEM V以及POSIX标准
⑥Socket:实现不同主机之间的进程间通信(网络通信)
二、信号(signal)概述:异步通信方式
1.信号的特点
①信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
②信号可以发生用户进程之间,发生在内核和用户进程之间 ③信号可以被阻塞,可以延迟送达,但信号不会丢失(如果该进程当前并未出于执行态,则该信号就由内核保存起来,直到该进程恢复到运行态再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程)
2.系统中的信号
①每个信号的名字都以字符SIG开头。 每个信号和一个数字编码相对应,在头文件signum.h中,这些信号都被定义为正整数。
②查看信号方式:
信号名定义路径:
vi /usr/include/i386-linux-gnu/bits/signum.h
用系统命令查看:kill -l
查帮助:man 7 signal
3.信号的产生方式
①用户产生,键盘输入
“Ctrl+c”表示产生SIGINT,“Ctrl+z”产生SIGSTOP,“Ctrl+\”产生SIGQUIT
②硬件异常产生信号
除数为0,无效内存访问(段错误),其他硬件问题
③软件异常也会产生信号
程序员设定检测,当出现异常时发信号
④直接调用kill命令(bash响应)
由bash调用系统调用kill函数
kill [ -signal | -s signal ] pid ...
kill -9 pid
kill -s KILL pid
KILL 不可阻塞,即强制杀死进程
kill -KILL pid
kill [ -L | -V, --version ]
kill -l [ signal ]//列出所有信号
⑤程序员直接调用kill函数
该信号发送者和接收者为相同的所有者,或者是发送者是超级用户
4.用户进程对信号的响应方式:
①捕捉信号(自定义处理方式)
用户自行编写处理该信号的处理函数
注意: SIGKILL和SIGSTOP不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。
②忽略信号
接收到此信号后不动作
③执行默认处理
绝大部分信号默认处理就是终止该进程。
5.信号的执行很快
三、信号的处理:接收和发送
1.信号的接收处理
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,
sighandler_t handler);
功能:
注册信号处理函数(不可用于SIGKILL、SIGSTOP信号),即确定收到信号后处理函数的入口地址。
参数:
signum:信号编号
handler的取值:
忽略该信号:SIG_IGN
执行系统默认动作:SIG_DFL
自定义信号处理函数:信号处理函数名
返回值:
成功:返回函数地址,该地址为此信号上一次注册的信号处理函数的地址。
失败:返回SIG_ERR
要求信号处理函数为可重入函数
可重入函数
可重入函数是指函数可以由多个任务并发使用,而不必担心数据错误。
编写可重入函数:
①不使用(返回)静态的数据、全局变量(除非用信号量互斥)。
②不调用动态内存分配、释放的函数。
③不调用任何不可重入的函数(如标准I/O函数)。 注:即使信号处理函数使用的都是可重入函数(常见的可重入函数),也要注意进入处理函数时,首先要保存errno的值,结束时,再恢复原值。因为,信号处理过程中,errno值随时可能被改变。
举例:模拟三种方式
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void sig_ctrl_c_handler(int signo)
{
if (signo==2)
printf("Receive SIGINT signal.If you want to quit,pls input ctrl+\\.\n");
}
int main()
{
signal(SIGQUIT,SIG_DFL);//SIGQUIT默认处理
signal(SIGHUP,SIG_IGN);//忽略SIGHUP信号
signal(SIGINT,sig_ctrl_c_handler);//自定义SIGINT信号
//后续程序模拟复杂的程序
int i=0;
while(1)
{
sleep(1);
printf("loop i=%d\n",++i);
}
}
2.对信号处理方式的继承关系
①exec启动进程
运用exec进程替换的进程不会继承原进程对信号的处理方式,新启动的进程会还原默认处理方式。
测试程序test.c
#include <stdio.h>
int main()
{
int i=0;
while(1)
{
sleep(1);
printf("这是测试代码\n");
}
return 0;
}
gcc test.c -o test
exec程序
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void sig_ctrl_c_handler(int signo)
{
if (signo==2)
printf("Receive SIGINT signal.If you want to quit,pls input ctrl+\\.\n");
}
int main()
{
signal(SIGINT,sig_ctrl_c_handler);//自定义SIGINT信号
//后续程序模拟复杂的程序
execl("./test","test",NULL);
return 0;
}
②用fork创建子进程
用fork创建子进程,子进程会继承父进程对信号的处理方式
fork程序测试
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void sig_ctrl_c_handler(int signo)
{
if (signo==2)
printf("Receive SIGINT signal.If you want to quit,pls input ctrl+\\.\n");
}
int main()
{
signal(SIGINT,sig_ctrl_c_handler);//自定义SIGINT信号
pid_t pid=fork();
if (pid<0)
{
perror("fork");
return 0;
}else if (pid ==0)
{
sleep(2);//子进程停2秒
printf("in child process\n");
int i=0;
while(1)
{
sleep(1);
printf("in child process,loop i=%d\n",++i);
}
}else
{ //父进程
wait(pid);
}
return 0;
}
3.信号的发送
①利用kill函数发送信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signum);
功能:
给指定进程发送信号。
参数:
pid的取值有4种情况:
pid>0: 将信号传送给进程ID为pid的进程。
pid=0: 将信号传送给当前进程所在进程组中的所有进程。
pid=-1: 将信号传送给系统内所有的进程。
pid<-1: 将信号传给指定进程组的所有进程。这个进程组号等于pid的绝对值。
signum:信号的编号
返回值:
成功返回 0,失败返回 -1。
②利用raise函数发信号
#include <signal.h>
int raise(int signum);
功能:
给调用进程本身送一个信号。
参数:
signum:信号的编号。
返回值:
成功返回 0,失败返回 -1。
举例:
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
pid_t pid=-1;
printf("kill() and raise() demo\n");
if ( (pid=fork())<0)
{
perror("fork");
exit(1);
}
if (pid>0)
{// 父进程
sleep(1);//确认子进程先运行
if (waitpid(pid,NULL,WNOHANG)==0)
{//查询子进程退出状态:没有退出
printf("Parent: kill child process(pid=%d)\n",pid);
kill(pid,SIGKILL);//杀它
}
}else{
printf("in child process:I will stop!\n");
// raise(SIGSTOP);//自杀
while(1) printf("xxxxxx\n");
exit(0);
}
}
③利用闹钟函数alarm发信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:
在seconds秒后,向调用进程发送一个SIGALRM信号,SIGALRM信号的默认动作是终止调用alarm函数的进程。
返回值: 若以前没有设置过定时器,或设置的定时器已超时,返回0;否则返回定时器剩余的秒数,并重新设定定时器。
④利用函数pause捕捉信号
#include <unistd.h>
int pause(void);
功能: 将调用进程挂起直至捕捉到信号为止。这个函数通常用于判断信号是否已到。
返回值:
直到捕获到信号,pause函数才返回-1,且errno被设置成EINTR。
举例:
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
void sig_alarm_handler(int signo)
{
if (signo==SIGALRM)
printf("Receive a SIGALRM signal!\n");
}
void sig_quit_handler(int signo)
{
if (signo==SIGQUIT)
{
printf("Receive a SIGQUIT signal!\n");
exit(0);
}
}
int main()
{
printf("alarm() and pause() demo\n");
signal(SIGALRM,sig_alarm_handler);//自定义SIGALRM信号
signal(SIGQUIT,sig_quit_handler);//自定义SIGQUIT信号
alarm(3);//3s的定时器
printf("After alarm()\n");
pause();//暂停程序
printf("alarm timeout!\n");
return 0;
}
⑤利用abort函数发信号
#include <stdlib.h>
void abort(void);
功能:
向进程发送一个SIGABRT信号,默认情况下进程会退出。
注意: 即使SIGABRT信号被加入阻塞集,一旦进程调用了abort函数,进程也还是会被终止,且在终止前会刷新缓冲区,关文件描述符。
⑥信号的扩展:
信号处理的另外一种处理方式
通过sigaction()函数//相当于signal()
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)
参数:
signum:信号
act:设置信号的心的处理方法和特性,是一个struct sigactionde 结构体变量指针
oldact:在设置新的方法,用来记录原来的特性指针变量
struct sigaction
{
void (*sa_handler) (int);//相当于原signal 信号处理函数
sigset_t sa_mask; //处理该信号时,屏蔽其他的信号,是一个信号集
int sa_flags;//设置信号的其他操作,参看linux C编程文档
void (*sa_restorer) (void);//暂时没用
}
注意:
sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;
sa_sigaction的原型是一个带三个参数,类型分别为int,struct siginfo *,void *,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。
sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。
sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。
sa_restorer已过时,POSIX不支持它,不应再使用。
因此,当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,类似下面的代码:
#include <signal.h>
……
void sig_handler_with_arg(int sig,siginfo_t *sig_info,void *unused){……}
int main(int argc,char **argv)
{
struct sigaction sig_act;
……
sigemptyset(&sig_act.sa_mask);
sig_act.sa_sigaction=sig_handler_with_arg;
sig_act.sa_flags=SA_SIGINFO;
……
}
typedef struct {
int si_signo;
int si_code;
union sigval si_value;
int si_errno;
pid_t si_pid;
uid_t si_uid;
void *si_addr;
int si_status;
int si_band;
} siginfo_t;
为SA_SIGINFO举例:
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
void sig_int_handler(int sig,siginfo_t *sig_info,void *unused)
{
printf("发送信号的进程ID:%d\n",sig_info->si_pid);
printf("发送的信号signo:%d\n",sig_info->si_signo);
}
int main()
{
struct sigaction act;
act.sa_handler=sig_int_handler;
act.sa_flags=SA_SIGINFO;//当该标志位是SA_SIGINFO,信号处理函数有三个参数。
sigemptyset(&act.sa_mask);//设屏蔽集为空
sigaddset(&act.sa_mask,SIGQUIT);//添加SIGQUIT到屏蔽集
//注册中断信号捕捉函数
sigaction(SIGINT,&act,NULL);
while(1)
pause();
return 0;
}
举例 :
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
void sig_alarm_handler(int signo)
{
if (signo==SIGALRM)
printf("Receive a SIGALRM signal!\n");
}
void sig_quit_handler(int signo)
{
if (signo==SIGQUIT)
{
printf("Receive a SIGQUIT signal!\n");
exit(0);
}
}
int main()
{
signal(SIGHUP,SIG_IGN);//忽略SIGHUP信号
#if 0
signal(SIGALRM,sig_alarm_handler);//自定义SIGALRM信号
signal(SIGQUIT,sig_quit_handler);//自定义SIGQUIT信号
#else
struct sigaction act,oldact;
act.sa_handler=sig_alarm_handler;
act.sa_flags=SA_RESTART;
sigaction(SIGALRM,&act,&oldact);
act.sa_handler=sig_quit_handler;
act.sa_flags=SA_RESTART;
sigaction(SIGQUIT,&act,&oldact);
#endif
alarm(3);//3s的定时器
printf("After alarm()\n");
pause();//暂停程序
printf("alarm timeout!\n");
return 0;
}
四、信号集
一个用户进程常常需要对多个信号做出处理。为了方便对多个信号进行处理,在Linux系统中引入了信号集。
信号集是用来表示多个信号的数据类型。
1.信号集数据类型
sigset_t
定义路径:
/usr/include/i386-linux-gnu/bits/sigset.h
2.信号集的相关函数
信号集相关的操作主要有如下几个函数:
sigemptyset
sigfillset
sigismember
sigaddset
sigdelset
①sigemptyset函数
#include <signal.h>
int sigemptyset(sigset_t *set);
功能:
初始化由set指向的信号集,清除其中所有的信号即初始化一个空信号集。
参数:
set:信号集标识的地址,以后操作此信号集,对set进行操作就可以了。
返回值:
成功返回 0,失败返回 -1。
②sigfillset函数
#include <signal.h>
int sigfillset(sigset_t *set);
功能:
初始化信号集合set, 将信号集合设置为所有信号的集合。
参数:
信号集标识的地址,以后操作此信号集,对set进行操作就可以了。
返回值:
成功返回 0,失败返回 -1。
③sigismember函数
#include <signal.h>
int sigismember(const sigset_t *set,int signum);
功能:
查询signum标识的信号是否在信号集合set之中。
参数:
set:信号集标识符号的地址。
signum:信号的编号。
返回值:
在信号集中返回 1,不在信号集中返回 0
错误,返回 -1
④sigaddset函数
#include <signal.h>
int sigaddset(sigset_t *set, int signum);
功能:
将信号signum加入到信号集合set之中。
参数:
set:信号集标识的地址。
signum:信号的编号。
返回值:
成功返回 0,失败返回 -1。
⑤sigdelset函数
#include <signal.h>
int sigdelset(sigset_t *set, int signum);
功能:
将signum所标识的信号从信号集合set中删除。
参数:
set:信号集标识的地址。
signum:信号的编号。
返回值:
成功:返回 0
失败:返回 -1
⑥信号阻塞集(屏蔽集、掩码) 每个进程都有一个阻塞集,它用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。
所谓阻塞并不是禁止传送信号,而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。
#include <signal.h>
int sigprocmask(int how,
const sigset_t *set, sigset_t *oldset);
功能:
检查或修改信号阻塞集,根据how指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由set指定,而原先的信号阻塞集合由oldset保存。
参数:
how:信号阻塞集合的修改方法。
set:要操作的信号集地址。
oldset:保存原先信号集地址。
how:
SIG_BLOCK:向信号阻塞集合中添加set信号集
SIG_UNBLOCK:从信号阻塞集合中删除set集合
SIG_SETMASK:将信号阻塞集合设为set集合
注:若set为NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset中。
返回值:
成功:返回 0
失败:返回 -1
信号集举例:
#include <signal.h>
#include <stdio.h>
int main()
{
//创建信号集
sigset_t sg;
//初始化为空集
sigemptyset(&sg);
//添加STOP信号,即使将其加入到屏蔽集,该信号仍然有效
sigaddset(&sg,SIGSTOP);//sigaddset(&sg,19);
//添加QUIT信号
sigaddset(&sg,SIGQUIT);//sigaddset(&sg,3);
//设置信号屏蔽,第三个参数是用来保存old信号集合的,如果不想保存,给NULL
sigprocmask(SIG_SETMASK,&sg,NULL);
//死循环
while(1);
return 0;
}
SIGKILL和SIGSTOP不仅不能更改信号的处理方式,也不可屏蔽。
⑦sigpending函数
#include <signal.h>
int sigpending(sigset_t *set)
作用:检查被阻塞的信号,并通过set返回
举例:
#include <signal.h>
#include <stdio.h>
int main()
{
//创建信号集
sigset_t sg;
//初始化为空集
sigemptyset(&sg);
//添加QUIT信号
sigaddset(&sg,SIGQUIT);//sigaddset(&sg,3);
//设置信号屏蔽,第三个参数是用来保存old信号集合的,如果不想保存,给NULL
sigprocmask(SIG_SETMASK,&sg,NULL);
//创建新的信号集
sigset_t set1;
//死循环
while(1)
{
//初始化set1为空集
sigemptyset(&set1);
//获取被屏蔽的信号,并且已经被阻塞
sigpending(&set1);
//判断QUIT信号是否被阻塞过??
if (sigismember(&set1,SIGQUIT)==1)
{
printf("QUIT信号被屏蔽并被阻塞过\n");
return 0;
}
else
{
printf("目前为止QUIT信号没有被阻塞过\n");
}
}
return 0;
}