此为牛客Linux C++和黑马Linux系统编程课程笔记。

文章目录

  • 0. 信号的概念
  • 1. Linux信号一览表
  • 2. 信号相关函数
  • 3. kill函数
  • 4. raise函数
  • 5. abort函数
  • 6. alarm函数
  • 7. setitimer函数
  • 8. signal函数
  • 9. 信号集
  • 10. 自定义信号集相关函数
  • 11. sigprocmask函数
  • 12. sigpending函数
  • 13. sigaction函数
  • 14. 内核实现信号捕捉过程

0. 信号的概念


A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。

信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。

每个进程收到的所有信号,都是由内核负责发送的,内核处理。

1. Linux信号一览表





红色为重点掌握的信号

2. 信号相关函数


3. kill函数

#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);

功能:给任何的进程或者进程组pid, 发送任何的信号 sig

参数:

  • pid :

< 0 : 将信号发送给指定的进程
= 0 : 将信号发送给当前的进程组
= -1 : 将信号发送给每一个有权限接收这个信号的进程
< -1 : 这个pid=某个进程组的ID取反 (-12345)

  • sig : 需要发送的信号的编号或者是宏值,0表示不发送任何信号

kill(getppid(), 9);能够杀死父进程;kill(getpid(), 9);能够杀死当前进程。

4. raise函数

#include <sys/types.h>
#include <signal.h>int raise(int sig);

功能:给当前进程发送信号;

参数:sig : 要发送的信号;

返回值:成功 0, 失败 非0。

相当于kill(getpid(), sig);

5. abort函数

#include <sys/types.h>
#include <signal.h>void abort(void);

功能: 发送SIGABRT(编号为6)信号给当前的进程,杀死当前进程;

相当于kill(getpid(), SIGABRT);raise(SIGBRT);

6. alarm函数

设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,函数会给当前的进程发送一个信号:SIGALARM。

参数:seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。

返回值:

  • 之前没有定时器,返回0
  • 之前有定时器,返回之前的定时器剩余的时间

常用:使用alarm(0)取消定时器,返回旧闹钟余下秒数。

每个进程都有且只有唯一个定时器。 比如:进程先执行了alarm(10),2秒后又执行了一个alarm(5),alarm(5)的返回值是8,因为之前有定时器,返回的是之前定时器的剩余时间。然后从现在起该进程还是只有一个定时器,定时5秒,因为后来的定时器会刷新之前的定时器。

注意,alarm定时是与与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸…无论进程处于何种状态,alarm都计时。

看以下示例程序:

#include <unistd.h>
#include <stdio.h>
int main()
{int i;alarm(1);for(i = 0; ; i++) {printf("%d\n", i);}return 0;
}

用定时器让程序执行1s后停止。
我们用time ./alarm来查看该程序的运行时间:

可以看到实际运行时间几乎是1秒,但是发现用户时间和系统时间加起来与总的运行时间不同,这是为什么呢。

实际执行时间 = 系统时间 + 用户时间 + 等待时间。程序的很多时间浪费在printf上了。

7. setitimer函数

#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时。

参数:

  • which : 定时器以什么时间计时,有以下三种参数,一般用第一种自然定时。
    ITIMER_REAL: 真实时间(自然定时),时间到达发送 SIGALRM 常用
    ITIMER_VIRTUAL: 用户时间,时间到达发送 SIGVTALRM
    ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达发送 SIGPROF
  • new_value: 设置定时器的属性
struct itimerval {      // 定时器的结构体struct timeval it_interval;  // 每个阶段的时间,间隔时间struct timeval it_value;     // 延迟多长时间执行定时器
};struct timeval {        // 时间的结构体time_t      tv_sec;     //  秒数     suseconds_t tv_usec;    //  微秒
};
  • old_value :记录上一次的定时的时间参数,一般不使用,指定NULL

如以下示例程序能够实现延迟3秒,每2秒发送一次信号。

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>// 过3秒以后,每隔2秒钟定时一次
int main() {struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0;
}

由于还没有介绍signal信号捕捉函数,setitimer发出的信号让程序终止,所以无法演示其周期性发送信号的功能,接下来介绍signal函数。

8. signal函数

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

功能:设置某个信号的捕捉行为

注意:并不是该函数来捕捉信号,该函数只是向内核注册对某个信号的捕捉行为。

参数:

  • signum: 要捕捉的信号
  • handler: 捕捉到信号要如何处理,可以有以下三种参数:
    - SIG_IGN : 忽略信号
    - SIG_DFL : 使用信号默认的行为
    - 回调函数 : 这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。

回调函数:
- 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
- 不是程序员调用,而是当信号产生,由内核调用
- 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。

返回值:

  • 成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL
  • 失败,返回SIG_ERR,设置错误号

注意:SIGKILL 和 SIGSTOP不能被捕捉,不能被忽略。

在setitimer的示例代码中加入signal后,示例代码如下:

void myfunc(int num) {printf("捕捉到了信号的编号是:%d\n", num);printf("xxxxxxx\n");
}// 过3秒以后,每隔2秒钟定时一次
int main() {// 注册信号捕捉// signal(SIGALRM, SIG_IGN);// signal(SIGALRM, SIG_DFL);// void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。signal(SIGALRM, myfunc);struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0;
}

signal的第二个参数传入函数地址,当当前进程捕捉到SIGALRM信号时,将执行程序员自定义的myfunc函数,myfun函数的int类型参数是捕捉到的信号的值(编号)。
程序运行结果如下:

程序运行3秒后第一次发出信号,程序输出一次,然后每隔2秒发出一次信号。

9. 信号集

一个进程的PCB中除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

阻塞信号集(信号屏蔽字): 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)

未决信号集:

  • 信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。
  • 信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。

    信号集本质上是一个64位的二进制数,其每一位的0或1代表着该位序号对应的信号的状态。

    当信号产生时,PCB中未决信号集中的该位立即置为1,然后去阻塞信号集的同样位置查看是否为1,如果阻塞信号集的对应位置也为1,说明该信号要阻塞,未决信号集的该位置保持1不变;直到阻塞解除,这个信号就被处理。

10. 自定义信号集相关函数

以下信号集相关的函数都是对自定义的信号集进行操作。

int sigemptyset(sigset_t *set);
  • 功能:清空信号集中的数据,将信号集中的所有的标志位置为0
  • 参数:set,传出参数,需要操作的信号集
  • 返回值:成功返回0, 失败返回-1
int sigfillset(sigset_t *set);
  • 功能:将信号集中的所有的标志位置为1
  • 参数:set:传出参数,需要操作的信号集
  • 返回值:成功返回0, 失败返回-1
int sigaddset(sigset_t *set, int signum);
  • 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
  • 参数:
    - set:传出参数,需要操作的信号集
    - signum:需要设置阻塞的那个信号
  • 返回值:成功返回0, 失败返回-1
int sigdelset(sigset_t *set, int signum);
  • 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
  • 参数:
    - set:传出参数,需要操作的信号集
    - signum:需要设置不阻塞的那个信号
  • 返回值:成功返回0, 失败返回-1
int sigismember(const sigset_t *set, int signum);
  • 功能:判断某个信号是否阻塞
  • 参数:
    - set:需要操作的信号集
    - signum:需要判断的那个信号
  • 返回值:
    1 : signum被阻塞
    0 : signum不阻塞
    -1 : 失败

一个用到以上函数的示例程序如下:

#include <signal.h>
#include <stdio.h>int main() {// 创建一个信号集sigset_t set;// 清空信号集的内容sigemptyset(&set);// 判断 SIGINT 是否在信号集 set 里int ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 添加几个信号到信号集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 判断SIGINT是否在信号集中ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 判断SIGQUIT是否在信号集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}// 从信号集中删除一个信号sigdelset(&set, SIGQUIT);// 判断SIGQUIT是否在信号集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}return 0;
}

11. sigprocmask函数

之前的信号集函数均是对自定义的信号集进行操作,那如何修改内核中的阻塞信号集呢?可以使用sigprocmask函数,用自定义的信号集设置内核阻塞信号集。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)。

参数:

  • how : 如何对内核阻塞信号集进行处理,有以下可选参数:

SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变(假设内核中默认的阻塞信号集是mask, mask | set)。

SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞(相当于 mask = mask & ~set)。

SIG_SETMASK: 用set覆盖内核中原来的值。

  • set :已经初始化好的用户自定义的信号集
  • oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL。

返回值: 成功:0 ;失败:-1,并设置错误号。

12. sigpending函数

#include <signal.h>
int sigpending(sigset_t *set);

功能:获取内核中的未决信号集。

参数:set,传出参数,保存的是内核中的未决信号集中的信息。

13. sigaction函数

sigaction函数通常用于替代signal函数,用来捕捉信号,同时自定义信号的处理动作。

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

功能:检查或者改变信号的处理。信号捕捉。

参数:

  • signum : 需要捕捉的信号的编号或者宏值(信号的名称)
  • act :捕捉到信号之后的处理动作
  • oldact : 上一次对信号捕捉相关的设置,一般不使用,传NULL即可

返回值: 成功 0 失败 -1

其中参数act的类型sigaction结构体定义如下:

struct sigaction {// 函数指针,指向的函数就是信号捕捉到之后的处理函数void     (*sa_handler)(int);// 不常用void     (*sa_sigaction)(int, siginfo_t *, void *);// 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。sigset_t   sa_mask;// 使用哪一个信号处理对捕捉到的信号进行处理// 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigactionint        sa_flags;// 被废弃掉了void     (*sa_restorer)(void);};

其中sa_sigaction和sa_restorer我们基本用不到,所以掌握以下三个即可:
① sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作。
sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
③ sa_flags:通常设置为0,表使用默认属性。

该函数与signal函数最大的区别就在于sa_mask上,sa_mask是程序员自定义的一个信号集,该信号集充当调用信号处理函数时的一个临时的阻塞信号集,也就是说:

进程正常运行时,默认PCB中有一个信号屏蔽字(阻塞信号集),假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为☆。

示例程序如下:

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void catchFunc(int signo) {printf("捕捉到了信号:%d \n", signo);
}int main() {struct sigaction act;act.sa_handler = catchFunc;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGQUIT); // 想要在捕捉函数中屏蔽SIGQUIT信号int res = sigaction(SIGINT, &act, NULL);if(res == -1) {perror("sigaction error");exit(1);}while(1);return 0;
}

执行结果如下:
每次在终端输入ctrl+c(产生SIGINT信号)时,输出:捕捉到了信号2。

当在键盘中输入ctrl+\(产生SIGQUIT)时, 程序退出。那么有个问题,程序中不是已经设置了sigaddset(&act.sa_mask, SIGQUIT);来屏蔽信号了吗?为什么输入ctrl+\时, 程序依然会退出?是因为sigaction函数设置的sa_mask只在信号处理函数执行中生效,输出语句后信号处理函数以及执行完毕。

再看下面示例程序:该程序让信号处理函数睡眠10秒。

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>void catchFunc(int signo) {printf("捕捉到了信号:%d \n", signo);sleep(10);printf("-----finish-----");
}int main() {struct sigaction act;act.sa_handler = catchFunc;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGQUIT); // 想要在捕捉函数中屏蔽SIGQUIT信号int res = sigaction(SIGINT, &act, NULL);if(res == -1) {perror("sigaction error");exit(1);}return 0;
}

运行程序后,输入ctrl+c,终端输出如下:

此时10秒以内,依然在执行信号捕捉函数catchFunc,也就是说当前sa_mask是生效的。此时我们输入crtl+\:

程序并没有退出,因为此时sa_mask中屏蔽了SIGQUIT信号。等待10秒过后:

发现程序自动退出,这是因为10秒过后信号捕捉函数catchFunc执行完毕,临时的阻塞信号集(sa_mask)失效,此时生效的是原PCB中的阻塞信号集,未决信号集(SIGQUIT处的值为1)查询到后阻塞信号集中SIGQUIT处的值是0后,SIGQUIT信号递达,程序退出。

这里还有一个值得注意的细节:

当信号捕捉函数catchFunc执行时,我输入了多个ctrl+c后,信号捕捉函数执行完毕后,只输出了一个“捕捉到了信号:2”,这是因为我们无论向当前进程发出多少个相同信号,未决信号集的对应位都是1,无法记录相同信号的数量,所以当临时阻塞信号集被取消后,只输出了一个“捕捉到了信号:2”。有以下结论:

阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)。

14. 内核实现信号捕捉过程

【Linux系统编程学习】信号、信号集以其相关函数相关推荐

  1. linux系统编程学习_(2)进程控制-- fork函数、exec函数族、回收子进程--孤儿进程僵尸进程、wait函数

    linux系统编程学习_(2)进程控制-- fork函数.exec函数族.回收子进程–孤儿进程僵尸进程.wait函数 进程控制 fork()函数 创建一个子进程. pid_t fork(void); ...

  2. linux线程并不真正并行,Linux系统编程学习札记(十二)线程1

    Linux系统编程学习笔记(十二)线程1 线程1: 线程和进程类似,但是线程之间能够共享更多的信息.一个进程中的所有线程可以共享进程文件描述符和内存. 有了多线程控制,我们可以把我们的程序设计成为在一 ...

  3. 嵌入式Linux系统编程学习之二常用命令

    嵌入式Linux系统编程学习之二常用命令 文章目录 嵌入式Linux系统编程学习之二常用命令 前言 一.常用命令 1.su(用户切换) 2.useradd(添加用户) 3.passwd(修改密码) 4 ...

  4. 嵌入式Linux系统编程学习之一目录结构

    嵌入式Linux系统编程学习之一目录结构 文章目录 嵌入式Linux系统编程学习之一目录结构 前言 一.Linux目录结构 前言 Linux目录结构 一.Linux目录结构 /bin:存放Linux的 ...

  5. 【Linux系统编程学习】Linux进程控制原语(fork、exec函数族、wait)

    此为牛客Linux C++和黑马Linux系统编程课程笔记. 1. fork函数 1.1 fork创建单个子进程 #include<unistd.h> pid_t fork(void); ...

  6. 【Linux系统编程学习】匿名管道pipe与有名管道fifo

    此为牛客Linux C++和黑马Linux系统编程课程笔记. 0. 关于进程通信 Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不到 ...

  7. Linux系统编程学习之《编程前的准备》

    在进行Linux系统编程钱,先来看看编程前的准备吧! 先说说我为什么学习Linux系统编程,因为我觉得现在Linux是IT行业的主流,学习一下Linux相关知识,对于学计算机专业的我来说肯定是有必要的 ...

  8. Linux系统编程----7(信号集,信号屏蔽,信号捕捉)

    信号集操作函数 内核通过读取未决信号集来判断信号是否应被处理.信号屏蔽字 mask 可以影响未决信号集.而我们可以在应 用程序中自定义 set 来改变 mask.已达到屏蔽指定信号的目的. 信号集设定 ...

  9. Linux系统编程---6(信号的机制,信号4要素,Linu常规信号表,定时器)

    信号的概念 信号在我们的生活中随处可见, 如:古代战争中摔杯为号:现代战争中的信号弹:体育比赛中使用的信号枪- 他们都有共性: 简单 不能携带大量信息,只能带一个标志. 满足某个特设条件才发送. Un ...

最新文章

  1. 构建基于Chromium的应用程序(Winform程序加载Html页面)
  2. [Swift]LeetCode496. 下一个更大元素 I | Next Greater Element I
  3. GameObject.DestroyImmediate(go, true)会使磁盘资源数据丢失,导致不可用
  4. react 项目总结
  5. 编译linux内核适用的编译器,编译Linux内核时,CC,LD和CC [M]输出的代码是什么?...
  6. 问答一:回答高中生关于前端的疑问
  7. 基于JAVA+SpringBoot+Mybatis+MYSQL的今日头条新闻网站
  8. 程序人生:软件测试 非技术性面试题【建议每个测试人观看】
  9. 4G模块使用记录移远EC20、BC20
  10. 2018年大数据趋势丨大数据的黄金时代
  11. android设计简单计算器代码下载,简单计算器实例
  12. lq 635色带安装
  13. java接收前端参数
  14. KMSpico 无后门下载
  15. ringbuffer java例子_Java RingBuffer.publish方法代碼示例
  16. 微信红包雨怎么抢_微信红包雨怎么发? 微信红包雨的操作方法?
  17. 开源 | 爱奇艺网络流量分析引擎QNSM及其应用
  18. 《用户至上:用户研究方法与实践(原书第2版)》一2.4 理解用户
  19. Lists.partition用法
  20. 新概念二册 Lesson 21 Mad or not?是不是疯了? ( 被动语态)

热门文章

  1. js中使用0 “” null undefined {}需要注意
  2. input type:text输入框点击输入,文字消失
  3. [转]SQL语句资料
  4. [转]“UPA 中国”北京行业聚会笔录
  5. 云南省农村信用社计算机岗位待遇如何,云南农村信用社薪资待遇如何?
  6. mysql 设置client char_mysql编码问题:show variables like “%char%”
  7. java jnlp_java – 调试JNLP启动应用程序
  8. vb.net mysql存储图片_怎么让VB.NET 上传图片到SQL 数据库只保存路径,图片保存到文件...
  9. Java PushbackReader ready()方法与示例
  10. 关键字驱动测试示例_带有示例的False关键字