1. 信号的基础
信号时一种常用的Linux进程间的通信方式,用户进程使用的Linux命令中有许多包含对信号的处理。深入讨论常用的Linux信号时有必要的,可以通过查询帮助文档的方式查询。
1.1 信号的基本概念
信号时一种进程通信的方式,又称为软中断,一个进程一旦收到信号就会打断原来的程序执行流程来处理该信号。由于进程不知道你是否收到信号,因此,信号通过这种进程间的通信方式是异步的。任何一个进程都可以发送信号并且接收信号。使用 kill -l 命令可以查看系统所支持的信号列表。
kill-l 能够查看信号的编号以及信号对应的宏。这些宏定义在signal.h中。信号编号从1号开始,所以没有0号信号。每个信号的具体意义可以使用Linux系统内带有的man命令查询。
1.2 产生信号
Linux环境下共有以下5种方式可以产生信号:
1. 用户按下某些终端键时,终端驱动程序会发送信号给前台进程,例如 Ctrl+C 产生SIGINT信号,Ctrl+\ 产生SIGQUIT 信号。在用户需要终止一个进程时经常使用这些信号。
2. 硬件异常产生信号,这些条件由硬件检测到并通过内核,然后内核向当进程发送适当的信号。例如,被0 除后产生SIGFPE 异常信号,无效内存访问产生SIGSEGV 信号,这就是在Linux环境下开发应用程序经常遇到的段错误。
3. 一个进程调用 kil(2) 函数可以发送信号给另一个进程
4. 可以用 kill(1) 命令发送信号给某个进程,kill(1) 命令也是调用 kill(2)函数实现的,如果不明确指定信号则发送 SIGTERM信号。kill 命令常用的使用方法是杀掉一个进程,许多人根据其命令的名判断它的功能。其实杀死一个进程只是kill 命令功能极小的一部分,是其向指定进程发送SIGTERM信号的结束。
5. 当内核检测到某软件条件发生时,也可以通过信号通知进程,例如,闹钟时间超时产生SIGALRM,该信号是由alarm函数所设置的定时器发送的。
不论是上述5种方法中那一种发送了信号,接收信号的进程都会暂停执行程序,转而处理接收到的信号。如果进程处于就绪状态,那么一旦进程得到CPU时间片将首先处理信号。如果进程处于挂起状态,那么接收信号将唤醒挂起进程,进程将首先处理信号。
1.3 处理信号
对于一个信号,Linux环境下的进程只有三种处理方式:
1. 忽略此信号,对其置之不理
2. 注册一个信号处理函数,并要求内核在接收到信号时切换到用户态用该处理函数,这种方式称为捕捉到一个信号。用户程序经常需要对某些信号做一些自定义的处理,例如,如果进程创建了临时文件,那么可能要为SIGTERM 信号编写一个信号处理函数以清除临时文件,这样被 kill 时也可以比较干净地终止。
3. 执行系统默认动作。不同的信号有不同的系统默认动作,系统所使用的默认动作只有两种。终结进程或者忽略信号。
2. 信号的影响
信号是一种传统的、方便的通信方式。当进程捕捉到信号时,不论进程当前执行到何处,都会先跳到信号处理函数中执行,从信号处理函数返回后再恢复先前的代码位置继续执行。因此信号也会产生一个副作用。
2.1 重入
当进程捕捉到信号时,不论进程当前执行到何处,都会跳到信号处理函数中执行,从信号处理函数返回后再恢复先前的代码位置继续执行。信号处理函数此时进程执行到何处。
在同一进程中,main函数(主线程)、其他线程以及信号处理函数都是各自独立的执行流程,它们是并行。如果一个进程有多个执行流程,并且这些执行流程访问相同的全局资源(全局变量等),就有可能出现冲突。
如果一个函数被不同的执行流程调用,有可能第一次调用还没返回就又一次进入该函数,则称为重入,这个函数访问一个全局的链表,有可能因为重入而造成错乱,称为不可重入函数。反之,如果一个函数只访问局部变量或参数,则称为可重入(reentrant)函数或者纯代码,或称为线程安全(thread-safe) 的函数。
如果一个函数符号以下条件之一则是不可重入的:
1. 使用了全局的数据,例如全局变量或静态变量
2. 调用了动态方法得到内存(调用malloc函数),因为动态分配内存的方法也是以链表来管理内存分配的,这种数据也是全局作用域的
3. 使用了标准 I/O 库,标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构
所以,归根结底所有使用具有全局作用域的数据都是函数,都是不可重入的,这种函数代码被chengwi非纯代码。
2.2 原子操作
原子类型 sig_atomic_t
C语言提供了volatile 限定符,如果将变量定义为volatile sig_atmoic_t a=0;那么即使制定了优化选项,编译器不会优化选项,编译器也不会优化掉对变量a内存单元的读写。
由于被多个流程访问的全局资源都应当使用 volatile限定符,所以可以得出结论,sig_atomic_t 类型的变量应该总是和volatile 限定符一起使用的。因为使用 volatile 限定符一起使用的。因为使用sig_atomic_t 类型的数据基本上都是被多个执行流程访问的全局变量。
2.3 中断系统调用
信号时一种常用的Linux进程间的通信方式,用户进程使用的Linux命令中有许多包含对信号的处理。深入讨论常用的Linux信号时有必要的,可以通过查询帮助文档的方式查询。
1.1 信号的基本概念
信号时一种进程通信的方式,又称为软中断,一个进程一旦收到信号就会打断原来的程序执行流程来处理该信号。由于进程不知道你是否收到信号,因此,信号通过这种进程间的通信方式是异步的。任何一个进程都可以发送信号并且接收信号。使用 kill -l 命令可以查看系统所支持的信号列表。
kill-l 能够查看信号的编号以及信号对应的宏。这些宏定义在signal.h中。信号编号从1号开始,所以没有0号信号。每个信号的具体意义可以使用Linux系统内带有的man命令查询。
1.2 产生信号
Linux环境下共有以下5种方式可以产生信号:
1. 用户按下某些终端键时,终端驱动程序会发送信号给前台进程,例如 Ctrl+C 产生SIGINT信号,Ctrl+\ 产生SIGQUIT 信号。在用户需要终止一个进程时经常使用这些信号。
2. 硬件异常产生信号,这些条件由硬件检测到并通过内核,然后内核向当进程发送适当的信号。例如,被0 除后产生SIGFPE 异常信号,无效内存访问产生SIGSEGV 信号,这就是在Linux环境下开发应用程序经常遇到的段错误。
3. 一个进程调用 kil(2) 函数可以发送信号给另一个进程
4. 可以用 kill(1) 命令发送信号给某个进程,kill(1) 命令也是调用 kill(2)函数实现的,如果不明确指定信号则发送 SIGTERM信号。kill 命令常用的使用方法是杀掉一个进程,许多人根据其命令的名判断它的功能。其实杀死一个进程只是kill 命令功能极小的一部分,是其向指定进程发送SIGTERM信号的结束。
5. 当内核检测到某软件条件发生时,也可以通过信号通知进程,例如,闹钟时间超时产生SIGALRM,该信号是由alarm函数所设置的定时器发送的。
不论是上述5种方法中那一种发送了信号,接收信号的进程都会暂停执行程序,转而处理接收到的信号。如果进程处于就绪状态,那么一旦进程得到CPU时间片将首先处理信号。如果进程处于挂起状态,那么接收信号将唤醒挂起进程,进程将首先处理信号。
1.3 处理信号
对于一个信号,Linux环境下的进程只有三种处理方式:
1. 忽略此信号,对其置之不理
2. 注册一个信号处理函数,并要求内核在接收到信号时切换到用户态用该处理函数,这种方式称为捕捉到一个信号。用户程序经常需要对某些信号做一些自定义的处理,例如,如果进程创建了临时文件,那么可能要为SIGTERM 信号编写一个信号处理函数以清除临时文件,这样被 kill 时也可以比较干净地终止。
3. 执行系统默认动作。不同的信号有不同的系统默认动作,系统所使用的默认动作只有两种。终结进程或者忽略信号。
2. 信号的影响
信号是一种传统的、方便的通信方式。当进程捕捉到信号时,不论进程当前执行到何处,都会先跳到信号处理函数中执行,从信号处理函数返回后再恢复先前的代码位置继续执行。因此信号也会产生一个副作用。
2.1 重入
当进程捕捉到信号时,不论进程当前执行到何处,都会跳到信号处理函数中执行,从信号处理函数返回后再恢复先前的代码位置继续执行。信号处理函数此时进程执行到何处。
在同一进程中,main函数(主线程)、其他线程以及信号处理函数都是各自独立的执行流程,它们是并行。如果一个进程有多个执行流程,并且这些执行流程访问相同的全局资源(全局变量等),就有可能出现冲突。
如果一个函数被不同的执行流程调用,有可能第一次调用还没返回就又一次进入该函数,则称为重入,这个函数访问一个全局的链表,有可能因为重入而造成错乱,称为不可重入函数。反之,如果一个函数只访问局部变量或参数,则称为可重入(reentrant)函数或者纯代码,或称为线程安全(thread-safe) 的函数。
如果一个函数符号以下条件之一则是不可重入的:
1. 使用了全局的数据,例如全局变量或静态变量
2. 调用了动态方法得到内存(调用malloc函数),因为动态分配内存的方法也是以链表来管理内存分配的,这种数据也是全局作用域的
3. 使用了标准 I/O 库,标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构
所以,归根结底所有使用具有全局作用域的数据都是函数,都是不可重入的,这种函数代码被chengwi非纯代码。
2.2 原子操作
原子类型 sig_atomic_t
C语言提供了volatile 限定符,如果将变量定义为volatile sig_atmoic_t a=0;那么即使制定了优化选项,编译器不会优化选项,编译器也不会优化掉对变量a内存单元的读写。
由于被多个流程访问的全局资源都应当使用 volatile限定符,所以可以得出结论,sig_atomic_t 类型的变量应该总是和volatile 限定符一起使用的。因为使用 volatile 限定符一起使用的。因为使用sig_atomic_t 类型的数据基本上都是被多个执行流程访问的全局变量。
2.3 中断系统调用