目录
前言
首先了解一下system-V标准,该标准与消息队列、共享内存和信号量有关。这篇不说共享内存,我在之前的文章里说过了,也不重点说消息队列因为这个不常用,只简单提一下,这回主要写linux的信号量和进程信号,以及相关函数操作。
消息队列简述
使用消息队列来实现数据传输。
本质:是内核中的优先级队列。
实现:多个进程通过访问同一个消息队列,以添加数据节点和获取数据节点来实现通信(这个方式是全通工)
函数:int msgget(key_t key,int msgflg);//创建或打开消息队列
key:是个键值, 多个进程可通key值来访问同一个消息队列.函数的返回值id 与key 有关。
msgflag:权限标志位
函数返回值:成功返回消息队列标识符,失败返回-1。
消息队列生命周期随内核,自带同步与互斥,其传输是一种数据块传输。
信号量简述
作用:实现进程间的同步与互斥
本质:一个计数器+pcb等待队列
P操作:对计时器减一,判断计数器是否大于等于0,如果是则返回,如果失败则阻塞。
V操作:对计数器加一,判断计数器如果小于等于0,则唤醒一个等待的进程
同步的实现:
通过计数器对共享资源进行计数,在获取资源之前进行P操作,产生一个资源后进行V操作。
互斥的实现:
初始化计数器为1,表示资源只有一个,访问前进行P操作,访问完后进行V操作。
进程信号
进程信号不等于信号量。
概念:软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
使用kill -l指令可查询所有信号
其中1~31是非可靠非实时信号
34~64是可靠实时信号(在信号堆积时会优先处理实时信号)
进程信号的产生:
硬件产生:比如ctrl+c(SIGINT),ctrl+z(SIGTSTP),ctrl+\(SIGQUIT)等。
软件产生:kill-sig pid命令,kill(),raise()等
注册:让进程中做标记,让进程知道自己收到了某个信号。
操作:在进程pcb中有个未决信号集pending,信号的注册就是在未决信号集中标记信 号以及添加信号的信息节点。
非可靠信号:若信号已经注册,则不作任何操作
可靠信号:无论信号是否注册,都会进行注册
注销:在信号被处理前,消除信号存在的痕迹(主要防止信号被重复定义)
操作:在pcb中删除信号信息节点,重置位图
非可靠信号:删除节点信息,直接位图重置
可靠信号:删除信息节点之后,确定没有相同节点才会重置位图
如果一个进程不能被kill杀死,那么有以下几种原因:
1、这是个僵尸进程
2、这个进程是停止态(对信号不处理)
3、信号被阻塞或被自定义处理
signal函数
信号的处理,使用signal函数
sighandler_t signal(int signum,sighandler_t handler);
signum:信号值 handler:信号要新指定的处理方式
handler:SIG_DEL-默认; SIG_IGN-忽略;自定义
返回值:成功返回信号原来的处理方式;失败返回-1(SIG_ERR)
示例:使用signal函数自定义SIGINT信号的处理方式
static int tem=0;
void func(){ //有信号时调用函数
if(1==tem++){ //当第二次接收到信号时
printf("exit success!\n");
exit(0); //第二次接收信号也就是按下第二个ctrl+c时退出
}
else{ //第一次接收信号时
printf("hellow\n");
}
}
int main(){
signal(SIGINT,func); //func函数用于处理SIGINT信号(ctrl+c)
while(1){
sleep(2);
printf("hello thankyou thankyou very much\n"); //无SIGINT信号时一直循环
}
return 0;
}
sigaction函数
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
signum:要捕获的信号
*act:接收到信号之后对信号进行处理的结构体
*oldact:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来做备份)。如果不需要备份,此处可以填NULL
示例:使用sigaction函数自定义SIGINT信号的处理方式
//在使用sigaction函数捕捉信号SIGINT,在执行信号捕捉后的处理函数是,屏蔽信号SIGQUIT
//在执行程序后 ctrl+c 会出现 2 signal is catched(2就是2号信号就是SIGINT)
//然后ctrl+\ 进程不会退出,ctrl+z将其停止
void sigcb(int signo){
printf("\n%d signal is catched\n",signo);
while(1);
}
int main(){
int ret;
struct sigaction act; //定义act这个结构体
act.sa_handler =sigcb; //使用sigcb这个函数来处理
sigemptyset(&act.sa_mask);//将act.sa_mask清0
sigaddset(&act.sa_mask,SIGQUIT); //添加信号SIGQUIT到act.sa_mask集合
//表示在处理信号时不希望受到其他信号影响
//所以后面按ctrl+\(SIGQUIT)就不会退出
act.sa_flags=0; //设为0是默认属性
ret=sigaction(SIGINT,&act,NULL); //捕捉SIGINT信号
if(ret<0){
printf("error\n");
exit(1);
}
while(1);//一直循环等待捕捉信号
return 0;
}
sigprocmask函数
信号的阻塞:阻塞一个信号表示收到这个信号后暂时不处理,直到解除阻塞之后进行处理
int sigprocmask(int how,sigset_t *set,sigset_t *oldset);
how:要对信号阻塞集合进行的操作类型:以下几种:
SIG_BLOCK:将set集合中的信号添加到阻塞集合中
SIG_UNBLOCK:从阻塞集合中移出set集合中的信号
SIG_SETMAS:将阻塞集合中的信号设置为阻塞集合的信号
oldset:用于保存修改前阻塞集合的信息,不使用置空即可
返回值:成功0,失败-1
示例:使用sigprocmask函数阻塞2号信号和40号信号,要求:阻塞2号信号和40号信号, 分别给进程发送5次2号信号和5次40号信号,观察结果
void sigcb(int signo){
printf("goda signal:%d\n",signo);
}
int main(){
signal(SIGINT,sigcb);
signal(SIGRTMIN+6,sigcb); //修改2号和40号信号的处理方式为sigcb函数
sigset_t set,old;
sigemptyset(&set); //清空集合
sigemptyset(&old);
sigfillset(&set); //将所有信号添加到set中
sigprocmask(SIG_BLOCK,&set,&old); //阻塞set中的信号
int i=5;
while(i--){ //给5次2号信号
kill(getpid(),2);
}
int j=5;
while(j--){ //给5次40号信号
kill(getpid(),40);
}
printf("Please input 'enter' to continue\n");
getchar();
//sigprocmask(SIG_SETMASK,&old,NULL); //按下回车后运行到此则解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1){ //这里执行完程序后不退出,可按ctrl+\退出
sleep(1);
}
return 0;
}
由上可见2号信号只打印了一次,40号信号打印了5次,因为2号信号是不可靠不实时信号,未决集合中存在,只会注册一次,产生再多也只会处理一次。而40号信号是实时信号,产生几次,注册几次。