对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。
信号概述
信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。
//1)挂起 2)中断(ctl+c) 3)退出 4)出现了问题 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP//6)丢弃 7)总线信号 9)杀死进程6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1// 14)闹钟信号
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
// 19)停止程序
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
// 29)IO口的访问
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
忽略信号:大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
捕捉信号:需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
系统默认动作:对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。(kill -9 pid 或者 kill -SIGKILL pid)杀死进程
具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。
信号处理函数的注册
信号处理函数的注册不只一种方法,分为入门版和高级版
(1)入门版:函数signal
(2)高级版:sigqueue
信号发送函数也不止一个,同样分为入门版和高级版
(1)入门版:kill
(2)高级版:sigqueue
signal
#include <signal.h>
typedef void (*sighandler_t)(int);//这个是函数指针,没有返回数,参数是int型,sighandler_t是函数名
sighandler_t signal(int signum, sighandler_t handler);//返回值是sighandler_t,第一个参数signum是要捕捉哪些信号,第二个参数handler是一种类型,指向函数指针void (*sighandler_t)(int)
代码演示
#include <signal.h>
#include<stdio.h>
// typedef void (*sighandler_t)(int);// sighandler_t signal(int signum, sighandler_t handler);void handler(int signum)//信号处理函数,捕捉信号
{printf("get signal=%d\n",signum);switch(signum){case 2:printf("SIGINT\n");break;case 9:printf("SIGKILL\n");case 10:printf("SIGUSER1\n");}printf("never quite\n");
}
int main()
{signal(SIGINT,handler);//如果将handler改为SIG_ICN即可忽略函数signal(SIGKILL,handler);signal(SIGUSR1,handler);while(1);
}
kill发送消息——低级版
#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);
代码实战
#include <signal.h>
#include<stdio.h>
#include <stdlib.h>
#include <sys/types.h>int main(int argc,char**argv)
{int signum;int pid;char cmd[128]={0};signum=atoi(argv[1]);pid=atoi(argv[2]);//atoi将阿斯科码转化为整数printf("num=%d,pid=%d\n",signum,pid);
// kill(pid,signum);//用来发信号sprintf(cmd,"kill -%d %d",signum,pid);//cmd是目标字符串,第二个参数是想要的目标字符串的长相system(cmd);//用system调用脚本发信号printf("send signal ok\n");return 0;
}
sigaction原型(安装信号处理程序)
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//第一个参数是信号,第二个是sigaction结构体用来绑定某些参数,第三个参数也是sigaction结构体,用来备份原有的信号操作,如不需要则设为NULL
struct sigaction {void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用,第一个参数是信号处理函数,第三个是指针空无数据,非空有数据sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
关于void (*sa_sigaction)(int, siginfo_t *, void );处理函数来说还需要有一些说明。void 是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。
siginfo_t {int si_signo; /* Signal number */int si_errno; /* An errno value */int si_code; /* Signal code */int si_trapno; /* Trap number that causedhardware-generated signal(unused on most architectures) */pid_t si_pid; /* Sending process ID */uid_t si_uid; /* Real user ID of sending process */int si_status; /* Exit value or signal */clock_t si_utime; /* User time consumed */clock_t si_stime; /* System time consumed */sigval_t si_value; /* Signal value 是结构体*/int si_int; /* POSIX.1b signal */void *si_ptr; /* POSIX.1b signal */int si_overrun; /* Timer overrun count; POSIX.1b timers */int si_timerid; /* Timer ID; POSIX.1b timers */void *si_addr; /* Memory location which caused fault */int si_band; /* Band event */int si_fd; /* File descriptor */
}
其中的成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。
信号发送函数——高级版
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);//第一个是发给谁,第二个是发的什么信号,第三个是消息
union sigval {int sival_int;void *sival_ptr;};
接收端代码实战
#include <signal.h>
#include<stdio.h>// int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
void handler(int signum, siginfo_t * info,void * context)
{printf("get signum:%d\n",signum);if(context!=NULL){printf("get data=%d\n",info->si_int);printf("get data=%d\n",info->si_value.sival_int);printf("from: %d\n",info->si_pid);}}
int main()
{struct sigaction act;printf("ps=%d\n",getpid());act.sa_sigaction=handler;//收到信号后调用handle处理信号act.sa_flags=SA_SIGINFO;//SA_SIGINFO表示能接收数据sigaction(SIGUSR1,&act,NULL);while(1);return 0;
}
发送端代码实战
#include<stdio.h>
#include <signal.h>
//int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char**argv)
{int signum;int pid;signum=atoi(argv[1]);pid=atoi(argv[2]);union sigval value;value.sival_int=100sigqueue(pid,signum,value);printf("pid =%d\n",getpid());printf("over\n"); return 0;
}
临界资源:
多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。
信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点
1、 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
2、信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
3、 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
4、 支持信号量组。
原型:
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1int semget(key_t key, int num_sems,int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops); // 控制信号量的相关信息int semctl(int semid, int sem_num, int cmd, ...);
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。
代码实战
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdio.h>
// int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);//取钥匙的函数,第一个参数是信号量ID,第二个是配置信号量的个数,第三个是第二项的个数
union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */};
void vPutBackKey(int id)
{struct sembuf set;set.sem_num=0;set.sem_op=+1;set.sem_flg=SEM_UNDO;semop(id,&set,1);printf("put back key\n");
}
void pGetKey(int id)
{struct sembuf set;set.sem_num=0;//信号量的编号set.sem_op=-1;//拿钥匙后原来的钥匙减一set.sem_flg=SEM_UNDO;//SEM_UNDO表示等待,IPC_NOWAIT表示不等待semop(id,&set,1);printf("get key\n");
}
int main (int argc,char**argv)
{int semid;key_t key=ftok(".",2);semid=semget(key,1,IPC_CREAT|0666);//第二个参数代表信号量集所含信号量个数,flag表示如果没有信号怎么做。IPC_CREAT表示若信号量不存在则创建它,0666表示信号量的权限union semun initsem;initsem.val=0;//刚开始里面是没有钥匙的状态semctl(sempid,0,SETVAL,initsem);//将信号量初始化,第一个参数是信号量集的ID,第二个参数是代表要操作第几个信号量 ,0代表第0个。第三个参数是要对信号量如何操作,SETVAL表示设置信号量的值设置为initsem,initsem.val=0表示有一把钥匙。pid_t pid=fork();if(pid>0){pGetKey(semid);printf("this is father\n");vPutBackKey(semid);semctl(semid,0,IPC_RMID);//销毁信号量}else if(pid==0){//pGetKey(semid);printf("this is child\n");vPutBackKey(semid);}else{printf("fork error\n");}return 0;
}
本文参考以下两篇博文编写:
信号量
信号