Linux信号专题一什么是信号
Posted Mindtechnist
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux信号专题一什么是信号相关的知识,希望对你有一定的参考价值。
欢迎加入【Linux C/C++/Python社区】一起探讨和分享Linux C/C++/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。
信号相关的概念
系统学习Linux,请关注本人免费专栏《Linux从小白到大神》。
1. 信号的概念
信号在生活中随处可见,比如体育比赛中使用的信号枪、我给你传递一个眼神(你懂的哈哈哈),等等。这些信号都有一些共同点:一是简单;而是不能携带大量信息;三是满足某个特设条件才发送。
信号是信息的载体,是Linux/UNIX 环境下,古老而经典的通信方式, 现在依然是主要的通信手段。Unix早期版本就提供了信号机制,但不可靠,信号可能丢失。Berkeley 和 AT&T都对信号模型做了更改,增加了可靠信号机制。但彼此不兼容。POSIX.1对可靠信号例程进行了标准化。
2. 信号的实现机制
进程A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。信号具有这样的特质,由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性,但是对于用户来说,这个延迟时间非常短,不易察觉。
每个进程收到的所有信号,本质上都是由内核负责发送的,由内核去处理,我们名义上说是进程A发送信号给进程B,实质上信号是由内核产生,由内核发送,并由内核处理的。进程收到信号要无条件处理信号,并且可以选择忽略(忽略也是对信号的一种处理)、捕捉、处理信号默认的动作等。
3. 信号相关的一些琐碎知识点
3.1 产生信号的方式
- 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\\
- 系统调用产生,如:kill、raise、abort
- 软件条件产生,如:定时器alarm,setitimer
- 硬件异常产生,如:非法访问内存(段错误)、浮点型错误、除0(浮点数例外)、内存对齐出错(总线错误)、SIGPIPE
- 命令产生,如:kill命令
3.2 信号的状态
- 产生:
- 递达:递送并且到达进程。
- 未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。
3.3 信号的处理方式
-
执行默认动作
-
忽略(丢弃)
-
捕捉(调用户处理函数),捕获可以说是学习信号最重要的原因之一,当我们的程序产生比如段错误、总线错误等等,程序会异常终止,我们通过捕获可以捕获到这种错误,并使程序不终止。
3.4 PCB中的信号相关信息
Linux内核的进程控制块PCB是一个结构体task_struct,除了包含进程id、状态、工作目录、用户id、组id、文件描述符表、还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
-
阻塞信号集:也叫信号屏蔽字,将某些信号加入集合,对他们设置屏蔽,当屏蔽某个信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)。
-
未决信号集:
- 信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态;当信号被处理对应位翻转回为0,这一时刻往往非常短暂。
- 信号产生后由于某些原因主要是阻塞不能抵达,这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
未决信号集就是没有被处理的信号,未决信号集实际上是一个32位数,每一位代表一个信号,当信号产生的时候,就把对应的位反转为1,如果该信号未被处理就反转回0,处理了就保持为1。
而阻塞信号集会影响到未决信号集,比如说我在阻塞信号集中将2号信号为置为1,也就是将2号信号屏蔽,那么未决信号集中2号信号对应的位就会变为1(未决状态),一直阻塞在这种状态。
3.5 信号的编号
可以使用 kill –l 命令查看当前系统有哪些可用信号
不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关,这些信号名字类似。
3.6 信号四要素
每个信号必须要有的四个要素:
- 编号
- 名称
- 事件
- 默认处理动作,信号的默认动作主要有:
- Term:终止进程
- Ign: 忽略信号 ,默认即时对该种信号忽略操作
- Core:终止进程,生成Core文件,主要是查验进程终止原因, 用于gdb调试
- Stop:停止(暂停)进程
- Cont:继续运行进程
3.7 信号帮助文档
可通过 man 7 signal 查看帮助文档获取标准信号信息表
在标准信号中,有一些信号是有三个 “Value”,第一个值通常对alpha和sparc架构有效,中间值针对x86、arm和其他架构,最后一个应用于mips架构。一个 ‘-’ 表示在对应架构上尚未定义该信号。我们主要关注中间的那个值。
不同的操作系统定义了不同的系统信号。因此有些信号出现在Unix系统内,也出现在Linux中,而有的信号出现在FreeBSD或 Mac OS 中却没有出现在Linux下。这里我们只研究Linux系统中的信号。
注意从 man 7 signal 帮助文档中可看到 : The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored. 这里特别强调了SIGKILL 和SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其设置为阻塞。另外需清楚,只有每个信号所对应的事件发生了,该信号才会被递送(但不一定递达)。
Linux进程间通信——使用信号量(转)
这篇文章将讲述别一种进程间通信的机制——信号量。注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物。有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信——使用信号。下面就进入信号量的讲解。
- int semget(key_t key, int num_sems, int sem_flags);
- int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
- struct sembuf{
- short sem_num;//除非使用一组信号量,否则它为0
- short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
- //一个是+1,即V(发送信号)操作。
- short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
- //并在进程没有释放该信号量而终止时,操作系统释放信号量
- };
- int semctl(int sem_id, int sem_num, int command, ...);
- union semun{
- int val;
- struct semid_ds *buf;
- unsigned short *arry;
- };
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <sys/sem.h>
- union semun
- {
- int val;
- struct semid_ds *buf;
- unsigned short *arry;
- };
- static int sem_id = 0;
- static int set_semvalue();
- static void del_semvalue();
- static int semaphore_p();
- static int semaphore_v();
- int main(int argc, char *argv[])
- {
- char message = ‘X‘;
- int i = 0;
- //创建信号量
- sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
- if(argc > 1)
- {
- //程序第一次被调用,初始化信号量
- if(!set_semvalue())
- {
- fprintf(stderr, "Failed to initialize semaphore\n");
- exit(EXIT_FAILURE);
- }
- //设置要输出到屏幕中的信息,即其参数的第一个字符
- message = argv[1][0];
- sleep(2);
- }
- for(i = 0; i < 10; ++i)
- {
- //进入临界区
- if(!semaphore_p())
- exit(EXIT_FAILURE);
- //向屏幕中输出数据
- printf("%c", message);
- //清理缓冲区,然后休眠随机时间
- fflush(stdout);
- sleep(rand() % 3);
- //离开临界区前再一次向屏幕输出数据
- printf("%c", message);
- fflush(stdout);
- //离开临界区,休眠随机时间后继续循环
- if(!semaphore_v())
- exit(EXIT_FAILURE);
- sleep(rand() % 2);
- }
- sleep(10);
- printf("\n%d - finished\n", getpid());
- if(argc > 1)
- {
- //如果程序是第一次被调用,则在退出前删除信号量
- sleep(3);
- del_semvalue();
- }
- exit(EXIT_SUCCESS);
- }
- static int set_semvalue()
- {
- //用于初始化信号量,在使用信号量前必须这样做
- union semun sem_union;
- sem_union.val = 1;
- if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
- return 0;
- return 1;
- }
- static void del_semvalue()
- {
- //删除信号量
- union semun sem_union;
- if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
- fprintf(stderr, "Failed to delete semaphore\n");
- }
- static int semaphore_p()
- {
- //对信号量做减1操作,即等待P(sv)
- struct sembuf sem_b;
- sem_b.sem_num = 0;
- sem_b.sem_op = -1;//P()
- sem_b.sem_flg = SEM_UNDO;
- if(semop(sem_id, &sem_b, 1) == -1)
- {
- fprintf(stderr, "semaphore_p failed\n");
- return 0;
- }
- return 1;
- }
- static int semaphore_v()
- {
- //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
- struct sembuf sem_b;
- sem_b.sem_num = 0;
- sem_b.sem_op = 1;//V()
- sem_b.sem_flg = SEM_UNDO;
- if(semop(sem_id, &sem_b, 1) == -1)
- {
- fprintf(stderr, "semaphore_v failed\n");
- return 0;
- }
- return 1;
- }
- #include <stdio.h>
- #include <stdlib.h>
- int main(int argc, char *argv[])
- {
- char message = ‘X‘;
- int i = 0;
- if(argc > 1)
- message = argv[1][0];
- for(i = 0; i < 10; ++i)
- {
- printf("%c", message);
- fflush(stdout);
- sleep(rand() % 3);
- printf("%c", message);
- fflush(stdout);
- sleep(rand() % 2);
- }
- sleep(10);
- printf("\n%d - finished\n", getpid());
- exit(EXIT_SUCCESS);
- }
以上是关于Linux信号专题一什么是信号的主要内容,如果未能解决你的问题,请参考以下文章