使用kill -l命令查看信号:
信号量和信号确实一点关系没有
信号是操作系统发出的进程与进程之间的通知于中断,是进程之间时间异步通知的一种方式
先了解同步通信:同步通信是一种比特同步通信技术,要求发收双方具有同频同相的同步时钟信号,只需在传送报文的最前面附加特定的同步字符,使发收双方建立同步,此后便在同步时钟的控制下逐位发送/接收。
优点是效率高,缺点是硬件要求高
什么叫异步通信:异步通信是指发送方在向接收方发送数据的时候可以有任意的时间间隔,没有严格的时序要求,而接收方需要一直存在,来接收数据
优点是便宜,缺点是效率低
信号量是一个计时器,表示可用资源的数量
进程产生信号的方式有四种
通过终端按键发出信号
例如你输入ctrl c的时候可以中断进程,ctrl \是离开信号,ctrl z是暂停信号
这些信号分别对应了上图的2信号,3信号,20信号
通过系统调用函数产生信号
kill命令在实现的时候也是调用kill函数实现的,kill函数可以给指定进程发送指定的信号
#include<signal.h>
int kill(pid_t pid,int signo);
来写一个kill函数的调用:
mykill:
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char *argv[]){
if(argc!=3){
perror("kill false");
return 1;
}
pid_t pid=atoi(argv[2]);
int sig=atoi(argv[1]);
kill(pid,sig);//啊啊啊啊,一开始写反参数的顺序了,气死了
return 0;
}
myprocess.c:
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
void handler(int sig){
printf("get a sig form myskill,sig==%d",sig);
}
int main(){
signal(2,handler);//signal是个系统调用函数,第一个参数是输入的信号是几,第二个参数有三种类型
//此处的类型为一个函数指针,在执行该语句的时候执行一个handler函数,这个函数是要自己声明的
//格式为int handler(int sig)
while(1){
printf("a process,pid==%d\n",getpid());
fflush(stdout);
sleep(1);
}
return 0;
}
除了kill函数还有abort函数,使当前进程接收到信号而异常终止
#include<stdlib.h>
void abort(void)
用go也写一个:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) //将sigint和sigterm的信号传给sig
//process start
//get a sig interrupt,这个interrupt就是syscall.SIGINT返回的信号
go func() {
sig := <-sigs//将信号发送给sig
fmt.Println("get a sig", sig)
done <- true//标为结束
}()
fmt.Println("process start")
<-done
}
myabort.c:
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
void handler(int sig){
printf("get a sig");
}
int main(){
signal(3,handler);//捕获信号
while(1){
sleep(1);
abort();//捕获之后就终止进程,因为abort就是终止
}
return 0;
}
那abort()函数收到信号会终止,那么是不是我让他捕捉所有的信号,就不能终止我的进程了?
void catchSig(int signum)
{
printf("get a signal:%d",signum);
}
int main()
{
for(int sig = 1; sig <= 31; sig++) {
signal(sig, catchSig);
}
while(true){
sleep(1);
}
return 0;
}
信号9不能被自动捕捉哦
但是我们还是可以用kill -9 pid来终止该进程:
还有raise,是给自己当前的进程发送信号:
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
void handler(int sig){
printf("get a sig,sig==%d\n",sig);
fflush(stdout);
}
int main(){
signal(3,handler);
while(1){
sleep(1);
raise(3);//给当前进程发送信号3
}
return 0;
}
由软件条件产生的信号
上图的信号13是一个管道中由软件产生的信号,读端关闭,写端一直写入的时候,os会直接啥的写端进程,怎么杀掉呢?通过向目标文件发送SIGPIPE(13)信号,终止目标进程,这就叫软件条件
由硬件异常生成的信号
硬件发生异常后以某种方式被检测到并且通知到内核,内核再向进程发送适当的信号(也是设计到软件条件产生的信号)
软件条件和硬件异常的产生的信号的区别:软件条件就是操作系统和应用程序内部生成的,硬件信号是物理设备(外设)通过中断机制发送
进程递达,阻塞和捕捉
一个信号在发送给一个进程的时候需要三个阶段:这个信号我收不收?这个信号我要不要写入?我写入/或者不写入用不用?
表现出来就是一个task_struct下的三个标志位:block(要不要阻塞,对应前面的这个信号我收不收),pending(bit位的位置表示编号,不同位置代表不同的信号,对应位置的信号写入,则置为1,否则置为0),例如,block 位图的最低一个比特位就表示是否对 1 号信号进行阻塞,pending 位图的最低一个比特位就表示是否收到 1 号信号。
bandler(这个信号我要不要忽视=这个信号我用不用)
阻塞和忽略是不同的,阻塞是不接收这个信号,忽略是接收这个信号,写入该信号,但是不执行这个信号;也就是说信号被阻塞就不能递达,递达之后才能选择你能不能忽略
信号递达:执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达(从有信号到执行信号的状态)称为信号未决(Pending)
信号集
能表示多个信号的一种数据类型,叫信号集(signal set,信号集其实就是 sigset_t 类型数据结构)
在阻塞信号集下的每个信号的“有效”和“无效”指的是这个信号有没有被阻塞,在未决信号集下的有无效是指是否处于未决状态
阻塞信号集也叫信号屏蔽字(Signal Mask),这里的屏蔽的意思==阻塞
sigset_t下的每个信号都用每个bit来判定信号是有效还是无效
设置信号集
通过函数设置信号集
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
我们来解释一下这些函数
sigemptyset(),用于清空(初始化)信号集,使其中所有的信号对应的bit清零,表示该信号集不包含有效信号
sigfillset(),也是初始化信号集内的信号,但是与sigemptyset()的区别是sigfillset()是置位,全部置为有效信号
每次使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,初始化后就可以调用其他的如sigaddset或sigdelset做添加或删除某种有效信号
这四个函数的返回值都是成功返回0,失败返回-1
而sigismember是个bool类型的函数,用于判断该信号集是否包含某种信号,包含返回1,不包含返回0,出错返回-1
维护信号集
调用函数sigprocmask()可以读取或更改进程的信号屏蔽字,也就是阻塞信号集
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
// 返回值:若成功则为0,若出错则为-1
若oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出(输出型参数)
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改
如果oset和set都是非空指针,则将原来的信号屏蔽字备份到oset里,然后根据how和set来修改信号屏蔽字
若mask是我们当前的信号屏蔽字,这是how的可选选项
如果调用 sigprocmask 解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
原理:一个信号被阻塞,也就是在信号屏蔽字内的时候,就处于未决状态;当调用过sigprocmask就意味着至少有一个信号得进入递达状态(内核至少递送一个信号)
读取当前进程的信号集
#include <signal.h>
sigpending(&s);
sigpending 函数可以读取当前进程的未决信号集,通过 s 参数传出。调用成功则返回0,出错则返回-1。
信号捕捉的过程
信号在内核态返回到用户态进行信号的检测和处理
如果处理信号的函数是用户自定义的函数,那么在信号递达的时候调用这个函数,称为捕捉信号。
在信号捕捉中,一共会涉及到四次状态转换
可重入函数
可重入函数(reentrant function)是指在多个执行流下,能够被同时调用而不会产生冲突或错误的函数。
这种函数能够保证在任意时刻,无论被同一个还是不同执行流调用,都能正确地完成预期的功能。
如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。