一、Linux C 信号机制
概念:信号是软件层面中断。信号的响应依赖中断。
同步:程序每一步执行什么内容是确定的。
异步:程序每一步执行什么内容是未知的。事件什么时候到来未知。
异步事件的处理:查询法;通知法。
文章目录
1 signal
查看信号命令: kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
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
其中编号 1~31 叫做标准信号,32~64 叫做实时信号。
- SIGINT为终端中断符,即在程序运行时在终端按下
ctrl
+c
引发的中断。
- signal 用来注册当前信号的行为。
void (*signal(int signum, void (*fun)(int)))(int);
SYNOPSIS
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
示例:
/**
* signal 函数示例
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void int_handler(int s){
write(1, "!", 1);
}
int main(){
//忽略 SIGINT 信号 ,SIG_IGN 为忽略信号
//signal(SIGINT, SIG_IGN);
//在程序未结束时,当收到SIGINT时,执行 int_handler 的动作
//信号会打断阻塞的系统调用,当一直按住 ctrl + c 时程序会“快进”执行。
signal(SIGINT, int_handler);
for (int i = 0; i < 10 ; i++){
write(1, "*" , 1);
sleep(1);
}
return 0;
}
这里体现了信号会打断阻塞的系统调用,这样在许多阻塞的系统调用发生错误时可能存在假错,我们需要对错误类型判断。如下:
do{
//尝试只读打开文件
fd = fopen(FILENAME, O_RDONLY);
if (fd < 0){ //如果打开失败
if (errno ! = EINTR){ //不是被信号打断,则报错退出
perroor("open()");
exit(1);
}
}
}while (fd < 0);//直到成功打开文件跳出循环
2 信号的不可靠
信号的不可靠指的是信号的行为是不可靠的。(不是指信号会丢失,但是标准信号一定会发生丢失,而实时信号不会)。一个信号在处理这个行为的同时又有一个相同的信号到达,由于现场的布置是由内核完成的,就有可能布置在同一个位置,使得第二次的现场将第一次的现场覆盖掉。
3 可重入函数
为了解决信号的不可靠问题。可重入函数是指第一次调用还没结束时发生第二次调用不会发生错误。
所有的系统调用都是可重入的,一部分的库函数也是可重入的,例:memcpy可以用在带有信号的程序中,而许多库函数为了能够在含有信号的情况下使用也添加了function_r版本的方法,以满足需求。
4 信号的响应过程
当前内核为每一个进程维护了两个位图,一个叫信号屏蔽字(mask),一个叫pending(一般都为32位)。
mask:记录信号的状态,一般情况初始全为1。
pending:用来记录发生了那些信号,一般情况初始全为0。
==当一个进程从内核态回到用户态时(从中断中恢复到现场),会有机会进行一次计算,计算 mask & pending,==如果结果全为0,则说明没有收到信号,进程返回原地址继续执行。
如果指定位的信号为1,说明收到了该信号。将栈内的返回现场地址悄悄改成该信号的处理函数的入口,将mask和pending的该信号的指示位都修改为0并执行信号处理函数。处理函数执行完毕后,将栈内的返回现场地址改回原地址,并将mask的指示位修改为1。
信号从收到到响应有不可避免的延迟,这个延迟是由有信号的响应机制是严重依赖系统的中断才能处理信号造成的。由于信号是由pending保存,位图不能够计数也不能累加故会存在信号丢失。标准信号 的响应没有严格的顺序。
不能随意地从信号处理函数中往外跳。(setjmp, longjmp)(sigsetjmp, siglongjmp)
5 常用函数
kill
- 发送信号
kill - send signal to a process
SYNOPSIS
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
DESCRIPTION
The kill() system call can be used to send any signal to any
process group or process.
If pid is positive, then signal sig is sent to the process
with the ID specified by pid.
If pid equals 0, then sig is sent to every process in the
process group of the calling process.//组内广播
If pid equals -1, then sig is sent to every process for
which the calling process has permission to send signals,
except for process 1 (init), but see below.//除了一号全局广播
If pid is less than -1, then sig is sent to every process in
the process group whose ID is -pid.//发送给指定进程组
If sig is 0, then no signal is sent, but existence and
permission checks are still performed; this can be used
to check for the existence of a process ID or process group
ID that the caller is permitted to signal.
//检测进程/进程组是否存在,检查返回错误值
For a process to have permission to send a signal,
it must either be privileged (under Linux: have the CAP_KILL
capability in the user namespace of the target process), or
the real or effective user ID of the sending process
must equal the real or saved set-user-ID of the target
process. In the case of SIGCONT, it suffices when the
sending and receiving processes belong to the same session.
(Historically, the rules were different; see NOTES.)
raise
- 给自己发信号
NAME
raise - send a signal to the caller
SYNOPSIS
#include <signal.h>
int raise(int sig);
DESCRIPTION
The raise() function sends a signal to the calling
process or thread. In a single-threaded program it is
equivalent to
kill(getpid(), sig);//以进程为单位
In a multithreaded program it is equivalent to
pthread_kill(pthread_self(), sig);//以线程为单位
If the signal causes a handler to be called, raise() will
return only after the signal handler has returned.
alarm
- 时钟信号,不能用于多任务 && pause
NAME
alarm - set an alarm clock for delivery of a signal
SYNOPSIS
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
DESCRIPTION
alarm() arranges for a SIGALRM signal to be delivered to the
calling process in seconds seconds.
If seconds is zero, any pending alarm is canceled.
In any event any previously set alarm() is canceled.
RETURN VALUE
alarm() returns the number of seconds remaining until any
previously scheduled alarm was due to be delivered,or zero
if there was no previously scheduled alarm.
//-------------------------------------------------------------
NAME
pause - wait for signal
SYNOPSIS
#include <unistd.h>
int pause(void);
DESCRIPTION
pause() causes the calling process (or thread) to sleep
until a signal is delivered that either terminates the
process or causes the invocation of a signal-catching
function.
RETURN VALUE
pause() returns only when a signal was caught and the
signal-catching function returned. In this case,
pause() returns -1, and errno is set to EINTR.
//-------------------------------------------------------------
/**
* alarm
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
alarm(5);
while (1){
pause();//等待一个信号打断
}
return 0;
}
不要轻易使用sleep()函数,因为有些环境下sleep是由 alarm + pause封装的,会对程序中的alarm使用造成影响。
简单实验
一、定时循环,设计程序定时5s循环计数。
- time版本
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
int main(){
time_t end;
long long int count = 0;
end = time(NULL) + 5;
while(time(NULL) <= end){
count ++;
}
printf("%lld\n", count);
return 0;
}
time版本会浪费大量时间在time的调用(取系统时戳)导致真正用在循环的时间非常少。效率极其低下。
- alarm版本
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int volatile loop = 1;//循环控制函数
void alrm_handler(int s){
loop = 0;
}
int main(){
long long int count;
signal(SIGALRM, alrm_handler);//注册信号行为,一定要在alarm之前
alarm(5);//时钟计时
while(loop){//循环条件
count++;
}
printf("%lld\n", count);
return 0;
}
//使用gcc -O1 优化编译,会导致永真循环,解决方案第七行加 volatile
//让程序不轻信内存中的值,要到变量真正的位置取值。。
二、流量控制
- 普通版(漏桶)
#define CPS 10 //每秒读写字符
#define BUFSIZE CPS
//最简单在循环内尾加sleep(1)
int volatile loop = 0;//循环控制函数
void alrm_handler(int s){
alarm(1);//为自己发送时钟信号
loop = 1;
}
signal(SIGALRM, alrm_handler);//注册信号行为
alarm(1);//时钟计时
while(1){
//控制时间
while(!loop){//此处存在忙等
;//pause(); //解决忙等问题
}
loop = 0;
//下面为实际循环内容
}
- 令牌桶
#define CPS 10 //每秒读写字符
#define BUFSIZE CPS
#define BURST 100 //桶大小上限
//最简单在循环内尾加sleep(1)
//int volatile token = 0;//循环控制函数
//sig_atomic_t 信号原子类型,这种类型的取值和赋值一定是原子操作
static volatile sig_atomic_t token =0;
void alrm_handler(int s){
alarm(1);//为自己发送时钟信号
token++;//token累加
if(token > BURST){
token = BURST;//限制token大小
}
}
signal(SIGALRM, alrm_handler);//注册信号行为
alarm(1);//时钟计时
while(1){
//控制时间
while(token <= 0){//此处存在忙等
pause(); //解决忙等问题
}
token--;//削减令牌
//下面为实际循环内容
}
桶令牌结构封装
- mytbf.h
#ifndef MYTBF_H__
#define MYTBF_H__
//令牌桶最数量
#define MYTBF_MAX 1024
//令牌桶结构类型
typedef void mytbf_t;
/**
* @name mytbf_init
* @brief 初始化一个令牌桶结构返回一个令牌桶结构指针,
* 并将结构体指针存放在令牌结构体指针数组全局变量中。
*
* @param cps 流控速率
* @param burst token最大值
*
* @return
* NULL - fail |
* mytbf_t类型的结构体指针 - succeed
*/
mytbf_t *mytbf_init(int cps, int burst);
/**
* @name mytbf_fetchtoken
* @brief 获取token并释放size大小的token
* @param ptr 桶令牌结构体指针
* @param size 需要释放的大小
* @return
* n 成功释放的大小 - succeed |
* -EINVAL errn - faild
*/
int mytbf_fetchtoken(mytbf_t *, int);
/**
* @name mytbf_returntoken
* @brief token归还函数
* @param ptr 桶令牌结构体指针
* @param size 归还的token大小
* @return
* size 成功累加 - succeed |
* -EINVAL errn - faild
*/
int mytbf_returntoken(mytbf_t *, int);
/**
* @name mytbf_destory
* @brief 销毁令牌结构体并释放空间
* @param ptr 桶令牌结构体指针
* @return
* 默认不会失败 返回 0 - succeed
*/
int mytbf_destory(mytbf_t *);
#endif
- mytbf.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include "mytbf.h"
//signal的返回值类型定义
typedef void (*sighandler_t)(int);
//存放令牌桶指针的全局变量
static struct mytbf_st* job[MYTBF_MAX];
//时钟信号使用标志位 0为可用 1为不可用
static int inited = 0;
//保存 SIGALRM 信号的原始行为
static sighandler_t alrm_handler_save;
//令牌桶结构体
struct mytbf_st {
//流控速率
int csp;
//令牌最大值 累加上限
int burst;
//令牌大小
int token;
//指针在 job[MYTBF_MAX] 存放位置
int pos;
};
/**
* @name get_free_pos
* @brief 获取令牌指针数组的空缺位置
* @param void
* @return
* pos 空缺位置数组下标 - succeed |
* -1 无空缺位置 - fail
*/
static int get_free_pos(void){
int i = 0;
//遍历数组
for(i = 0; i < MYTBF_MAX; i++){
//如果存在空指针 返回数组下标
if(job[i] == NULL){
return i;
}
}
//无空缺位置 返回-1
return -1;
}
/**
* @name alrm_handler
* @brief 时钟信号的处理函数,桶令牌的累加处理
*/
static void alrm_handler(int s){
//设置下一次的时钟信号
alarm(1);
//遍历桶令牌指针数组
for(int i = 0; i < MYTBF_MAX; i++){
//指针非空时对token进行累加计数
if(job[i] != NULL){
job[i]->token += job[i]->cps;
if(job[i]->token > job[i]->burst){
job[i]->token = job[i]->burst;
}
}
}
}
/**
* @name module_unload
* @brief 桶令牌解挂函数,恢复SIGALRM信号的原始行为,并清空时钟信号。
* @param void
* @return void
*/
static void module_unload(void){
//恢复SIGALRM信号的原始行为
signal(SIGALRM, alrm_handler_save);
//并清空时钟信号
alarm(0);
//释放令牌空间,并将指针指空
for(int i = 0; i < MYTBF_MAX; i++){
free(job[i]);
job[i] = NULL;
}
}
/**
* @name module_load
* @brief 重新定义SIGALRM信号的行为,保留SIGALRM信号的原始行为,并设置时钟信号。
* @param void
* @return void
*/
static void module_load(void){
//重新定义SIGALRM信号的行为,保留SIGALRM信号的原始行为
alrm_handler_save = signal(SIGALRM, alrm_handler);
//设置时钟信号
alarm(1);
//异常退出处理(钩子函数)
atexit(module_unload);
}
/**
* @name mytbf_init
* @brief 初始化一个令牌桶结构返回一个令牌桶结构指针,
* 并将结构体指针存放在令牌结构体指针数组全局变量中。
*
* @param cps 流控速率
* @param burst token最大值
*
* @return
* NULL - fail |
* mytbf_t类型的结构体指针 - succeed
*/
mytbf_t *mytbf_init(int cps, int burst){
//声明结构体指针
struct mytbf_st *me;
//桶令牌指针位置
int pos;
//判断时钟是否可用
if(!inited){
//初始化时钟信号,和时钟触发机制
module_load();
//将inited置1,时钟信号以初始化,时钟信号不再初始化。
inited = 1;
}
//获取令牌指针数组中的空缺位置
pos = get_free_pos();
//数组已满 无法初始化 返回NULL
if(pos < 0){
return NULL;
}
//申请空间并初始化结构体成员
me = malloc(sizeof(*me));
//分配内存失败 返回空指针
if (me == NULL){
return NULL;
}
me->burst = burst;
me->csp = cps;
me->pos = pos;
me->token = 0;
//将指针存入 指针数组空缺位置中
job[pos] = me;
//返回结构体指针
return me;
}
/**
* @name mytbf_fetchtoken
* @brief 获取token并释放size大小的token
* @param ptr 桶令牌结构体指针
* @param size 需要释放的大小
* @return
* n 成功释放的大小 - succeed |
* -EINVAL errn - faild
*/
int mytbf_fetchtoken(mytbf_t *ptr, int size){
//指针类型强转
struct mytbf_st *me = ptr;
//释放大小
int n;
//size的合法性判断
if(size <= 0){
return -EINVAL;
}
//当token无内容时 等待时钟信号触发
while(me->token <= 0){
pause();
}
//选择 token和size的最小值释放空间
n = min(me->token, size);
me->token -= n;
//返回成功释放空间的大小
return n;
}
/**
* @name mytbf_returntoken
* @brief token归还函数
* @param ptr 桶令牌结构体指针
* @param size 归还的token大小
* @return
* size 成功累加 - succeed |
* -EINVAL errn - faild
*/
int mytbf_returntoken(mytbf_t *ptr, int size){
//指针类型强转
struct mytbf_st *me = ptr;
//size的合法性判断
if(size <= 0){
return -EINVAL;
}
//token增加
me->token += size;
//判断token是否溢出
if(me->token > me->burst){
me->token = me->burst;
}
//成功返回
return size;
}
/**
* @name mytbf_destory
* @brief 销毁令牌结构体并释放空间
* @param ptr 桶令牌结构体指针
* @return
* 默认不会失败 返回 0 - succeed
*/
int mytbf_destory(mytbf_t *ptr){
//指针类型强转
struct mytbf_st *me = ptr;
//释放指针指向的结构体
free(ptr);
//将待删除位置指针指控
job[me->pos] = NULL;
//成功返回
return 0;
}
mian.c
#include <stdio.h>
#include <stdlib.h>
#include "mytbf.h"
#define CPS 10
#define BURST 100
#define BUFSIZE 1024
int main(){
mytbf_t *tbf;
int size, len;
//所有要实现的内容之前调用init
tbf = mytbf_init(CPS,BURST);
if(tbf == NULL){
exit(0);//错误退出
}
while(1){
//向token询问 BUFSIZE 个使用权限,返回size个可使用的权限
size = mytbf_fetchtoken(tbf, BUFSIZE);
//下面使用 size个可使用权限
//例如读 size个大小的字符 确定读到 len个大小的字符
if(size - len > 0){
//归还剩余token
mytbf_returntoken(tbf, size-len);
}
}
//销毁令牌桶
mytbf_destory(tbf);
return 0;
}
思考题
- 使用单一计时器,构造一组函数,实现任意数量的计时器。
数据结构构造:
成员 | 数据类型 | 描述 |
---|---|---|
sec | int | 时间 |
func | 函数指针r | 行为函数入口 |
arg | char* | 字符串 |
每次对sec的值自减,当sec为0时调用函数并传入参数。
setitimer
- setitimer
NAME
getitimer, setitimer - get or set value of an interval timer
SYNOPSIS
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value); //误差不累计
DESCRIPTION
These system calls provide access to interval timers, that is, timers that initially expire at some point in the future, and (optionally) at regular intervals after that. When a timer expires, a signal is generated for the calling process, and the timer is reset to the specified interval (if the interval is nonzero).
Three types of timers—specified via the which argument—are provided, each of which counts against a different clock and generates a different signal on timer expiration:
//实际时间
ITIMER_REAL This timer counts down in real (i.e., wall clock) time.
At each expiration, a SIGALRM signal is generated.
//用户模式 (所有线程)
ITIMER_VIRTUAL This timer counts down against the user-mode CPU time
consumed by the process. (The measurement includes CPU time
consumed by all threads in the process.) At each expiration,
a SIGVTALRM signal is generated.
//系统+用户(所有线程)
ITIMER_PROF This timer counts down against the total (i.e., both user and
system) CPU time consumed by the process. (The measurement
includes CPU time consumed by all threads in the process.) At
each expiration, a SIGPROF signal is generated.
process. (The measurement includes CPU time consumed by all
threads in the process.) At each expiration, a SIGPROF signal
is generated.
In conjunction with ITIMER_VIRTUAL, this timer can be used to
profile user and system CPU time consumed by the process.
A process has only one of each of the three types of timers.
Timer values are defined by the following structures:
struct itimerval {
struct timeval it_interval; //周期 /* Interval for periodic timer */
struct timeval it_value; //初相位 /* Time until next expiration */
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
//tv_sec 和 tv_usec 到 it_value 的赋值时原子化的操作
abort
- abort 给自己发送一个SIGABRT 信号
NAME
abort - cause abnormal process termination //导致进程异常终止
SYNOPSIS
#include <stdlib.h>
void abort(void);
DESCRIPTION
The abort() first unblocks the SIGABRT signal, and then raises that signal for the calling process (as though raise(3) was called). This results in the abnormal termination of the process unless the SIGABRT signal is caught and the signal handler does not return (see longjmp(3)).
If the SIGABRT signal is ignored, or caught by a handler that returns, the abort() function will still terminate the process. It does this by restoring the default disposition for SIGABRT and then raising the signal for a second time.
RETURN VALUE
The abort() function never returns.
system
- system
NAME
system - execute a shell command
SYNOPSIS
#include <stdlib.h>
int system(const char *command);
system - execute a shell command
SYNOPSIS
#include <stdlib.h>
int system(const char *command);
DESCRIPTION
The system() library function uses fork(2) to create a child process that executes the shell command specified in command using execl(3) as follows:
execl("/bin/sh", "sh", "-c", command, (char *) 0);
system() returns after the command has been completed.
/* 在带信号的程序中使用 要 block SIGCHLD 并 忽略掉 SIGINT 和 SIGQUIT */
During execution of the command, SIGCHLD will be blocked, and SIGINT and
SIGQUIT will be ignored, in the process that calls system() (these signals
will be handled according to their defaults inside the child process that executes
command).
If command is NULL, then system() returns a status indicating whether a shell is available on the system
6 信号集
相关函数
- sigemptyset
//sigset_t 信号集类型
NAME
sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX signal set operations
SYNOPSIS
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
sigemptyset(), sigfillset(), sigaddset(), sigdelset(), sigismember():
_POSIX_C_SOURCE
DESCRIPTION
These functions allow the manipulation of POSIX signal sets.
sigemptyset() initializes the signal set given by set to empty, with all signals excluded from the set.
sigfillset() initializes set to full, including all signals.
sigaddset() and sigdelset() add and delete respectively signal signum from set.
sigismember() tests whether signum is a member of set.
Objects of type sigset_t must be initialized by a call to either sigemptyset() or sigfillset() before being passed to the functions sigaddset(), sigdelset() and sigismember() or the additional glibc functions described below (sigisemptyset(), sigandset(), and sigorset()). The results are undefined if this is not done.
RETURN VALUE
sigemptyset(), sigfillset(), sigaddset(), and sigdelset() return 0 on success and -1 on error.
sigismember() returns 1 if signum is a member of set, 0 if signum is not a member, and -1 on error.
On error, these functions set errno to indicate the cause of the error.
7 信号屏蔽字/pending集
相关函数
- sigprocmask
NAME
sigprocmask, rt_sigprocmask - examine and change blocked signals
SYNOPSIS
#include <signal.h>
/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/* Prototype for the underlying system call */
int rt_sigprocmask(int how, const kernel_sigset_t *set,
kernel_sigset_t *oldset, size_t sigsetsize);
/* Prototype for the legacy system call (deprecated) */
int sigprocmask(int how, const old_kernel_sigset_t *set,
old_kernel_sigset_t *oldset);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
sigprocmask(): _POSIX_C_SOURCE
DESCRIPTION
sigprocmask() is used to fetch and/or change the signal mask of the calling thread. The signal mask is the
set of signals whose delivery is currently blocked for the caller (see also signal(7) for more details).
The behavior of the call is dependent on the value of how, as follows.
SIG_BLOCK
The set of blocked signals is the union of the current set and the set
argument.
SIG_UNBLOCK
The signals in set are removed from the current set of blocked signals.
It is permissible to attempt to unblock a signal which is not
blocked.
SIG_SETMASK
The set of blocked signals is set to the argument set.
If oldset is non-NULL, the previous value of the signal mask is stored in
oldset.
If set is NULL, then the signal mask is unchanged (i.e., how is ignored), but
the current value of the signal
mask is nevertheless returned in oldset (if it is not NULL).
A set of functions for modifying and inspecting variables of type sigset_t
("signal sets") is described in sigsetops(3).
The use of sigprocmask() is unspecified in a multithreaded process; see
pthread_sigmask(3).
示例:
/**
* 信号屏蔽 函数示例
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void int_handler(int s){
write(1, "!", 1);
}
int main(){
//set 信号集 oset 旧状态 saveset 原始状态
sigset_t set, oset, saveset;
signal(SIGINT, int_handler);
//置空信号集
sigemptyset(&set);
//添加 SIGINT信号到信号集中
sigaddset(&set, SIGINT);
//采集原始信号集中的信号状态 保存到 saveset
sigprocmask(SIG_UNBLOCK, &set, &saveset);
for (int j = 0; j < 1000; j++){
//阻塞信号集中的信号,保存旧的状态到oset
sigprocmask(SIG_BLOCK, &set, &oset);
for (int i = 0; i < 5 ; i++){
write(1, "*" , 1);
sleep(1);
}
write(1, "\n", 1);
//从oset中恢复旧的状态
sigprocmask(SIG_SETMASK, &oset, NULL);
}
//结束前恢复原始状态
sigprocmask(SIG_SETMASK, &saveset, NULL);
return 0;
}
- sigpending
NAME
sigpending, rt_sigpending - examine pending signals
//从内核中获取pending的状态,基本无用,因为信号响应的机制。
SYNOPSIS
#include <signal.h>
int sigpending(sigset_t *set);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
sigpending(): _POSIX_C_SOURCE
SYNOPSIS
#include <signal.h>
int sigpending(sigset_t *set);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
sigpending(): _POSIX_C_SOURCE
DESCRIPTION
sigpending() returns the set of signals that are pending for delivery to the calling thread (i.e., the signals which have been raised while blocked). The mask of pending signals is returned in set.
RETURN VALUE
sigpending() returns 0 on success and -1 on error. In the event of an error, errno is set to indicate the
cause.
8 扩展
- sigsuspend 信号驱动
NAME
sigsuspend, rt_sigsuspend - wait for a signal
SYNOPSIS
#include <signal.h>
int sigsuspend(const sigset_t *mask);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
sigsuspend(): _POSIX_C_SOURCE
DESCRIPTION
SYNOPSIS
#include <signal.h>
int sigsuspend(const sigset_t *mask);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
sigsuspend(): _POSIX_C_SOURCE
DESCRIPTION
sigsuspend() temporarily replaces the signal mask of the calling process with the mask given by mask and then
suspends the process until delivery of a signal whose action is to invoke a signal handler or to terminate a
process.
If the signal terminates the process, then sigsuspend() does not return. If the signal is caught, then sig‐
suspend() returns after the signal handler returns, and the signal mask is restored to the state before the
call to sigsuspend().
It is not possible to block SIGKILL or SIGSTOP; specifying these signals in mask, has no effect on the
process's signal mask.
例:
/**
* 信号驱动 函数示例
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void int_handler(int s){
write(1, "!", 1);
}
int main(){
//set 信号集 oset 旧状态 saveset 原始状态
sigset_t set, oset, saveset;
signal(SIGINT, int_handler);
//置空信号集
sigemptyset(&set);
//添加 SIGINT信号到信号集中
sigaddset(&set, SIGINT);
//采集原始信号集中的信号状态 保存到 saveset
sigprocmask(SIG_UNBLOCK, &set, &saveset);
sigprocmask(SIG_BLOCK, &set, &oset);//
for (int j = 0; j < 1000; j++){
//阻塞信号集中的信号,保存旧的状态到oset
//sigprocmask(SIG_BLOCK, &set, &oset);
for (int i = 0; i < 5 ; i++){
write(1, "*" , 1);
sleep(1);
}
write(1, "\n", 1);
/*
//非原子化操作,信号容易在pause之前被相应,无法完成驱动任务
//从oset中恢复旧的状态
sigprocmask(SIG_SETMASK, &oset, NULL);
pause();
*/
//解决方案
sigsuspend(&oset);
/** 相当于一下内容地原子化操作
* 最外层循环 28L 添加
* sigprocmask(SIG_BLOCK, &set, &oset);
*
* 内循环此处添加
* sigset_t tmpset;
* sigprocmask(SIG_SETMASK, &oset, &tmpset);
* pause();
* sigprocmask(SIG_SETMASK, &tmpset, NULL);
*/
}
//结束前恢复原始状态
sigprocmask(SIG_SETMASK, &saveset, NULL);
return 0;
}
//效果每行打印5个*后停止,在接收到 ctrl + c 的信号后驱动继续打印一行,
//并保证能在打印过程中等待响应阻塞的信号
- sigaction
代替 signal 函数,解决signal在处理多个信号时容易发生重入的问题。
解决无法判断信号来源
//通过信号关闭文件描述符以及日志
void xxx_exit(int s){
/*
if(s == SIGINT){
}
else if(s == SIGQUIT){
}
else if(s == SIGTERM){
}
else;
*/
fclose(fp);
closelog();
exit(0);
}
int main(){
...
signal(SIGINT, xxx_exit);
signal(SIGQUIT, xxx_exit);
signal(SIGTERM, xxx_exit);
...
}
//有重入的危险,当信号处理函数在响应的时候容易被再次响应
sigaction说明
NAME
sigaction, rt_sigaction - examine and change a signal action
SYNOPSIS
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
sigaction(): _POSIX_C_SOURCE
siginfo_t: _POSIX_C_SOURCE >= 199309L
DESCRIPTION
The sigaction() system call is used to change the action taken by a process on receipt of a specific signal. (See signal(7) for an overview of signals.)
signum specifies the signal and can be any valid signal except SIGKILL and SIGSTOP.
If act is non-NULL, the new action for signal signum is installed from act. If oldact is non-NULL, the previous action is saved in oldact.
The sigaction structure is defined as something like:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
//void* 执行信号处理函数而被打断的现场,实质上这里的void* 是ucontext_t *
//int getcontext(ucontext_t *ucp);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
//sa_handler 和 sa_sigaction 在很多结构中是共用体不能够同时指定
//sa_mask 在响应信号时希望那些信号被阻塞
//sa_flags 特殊指令
On some architectures a union is involved: do not assign to both sa_handler and sa_sigaction.
The sa_restorer field is not intended for application use. (POSIX does
not specify a sa_restorer field.) Some further details of the purpose of this
field can be found in sigreturn(2).
sa_handler specifies the action to be associated with signum and may be
SIG_DFL for the default action, SIG_IGN to ignore this signal, or a
pointer to a signal handling function. This function receives the signal
number as its only argument.
If SA_SIGINFO is specified in sa_flags, then sa_sigaction (instead of
sa_handler) specifies the signal-handling function for signum. This
function receives three arguments, as described below.
sa_mask specifies a mask of signals which should be blocked (i.e., added to
the signal mask of the thread in which the signal handler is invoked) during
execution of the signal handler. In addition, the signal which triggered
the handler will be blocked, unless the SA_NODEFER flag is used.
sa_flags specifies a set of flags which modify the behavior of the signal.
It is formed by the bitwise OR of zero or more of the following:
SA_NOCLDSTOP
If signum is SIGCHLD, do not receive notification when child
processes stop (i.e., when they receive one of SIGSTOP,
SIGTSTP, SIGTTIN, or SIGTTOU) or resume (i.e., they receive
SIGCONT) (see wait(2)). This flag is meaningful only when
establishing a handler for SIGCHLD.
SA_NOCLDWAIT (since Linux 2.6)
If signum is SIGCHLD, do not transform children into zombies when
they terminate. See also waitpid(2). This flag is meaningful
only when establishing a handler for SIGCHLD, or when setting that
signal's disposition to SIG_DFL.
If the SA_NOCLDWAIT flag is set when establishing a handler for
SIGCHLD, POSIX.1 leaves it unspecified whether a SIGCHLD signal
is generated when a child process terminates. On Linux, a SIGCHLD
signal is generated in this case; on some other implementations, it
is not.
SA_NODEFER
Do not prevent the signal from being received from within its own
signal handler. This flag is meaningful only when establishing
a signal handler. SA_NOMASK is an obsolete, nonstandard synonym
for this flag.
SA_ONSTACK
Call the signal handler on an alternate signal stack provided by
sigaltstack(2). If an alternate stack is not available, the
default stack will be used. This flag is meaningful only when
establishing a signal handler.
SA_RESETHAND
Restore the signal action to the default upon entry to the signal
handler. This flag is meaningful only when establishing a
signal handler. SA_ONESHOT is an obsolete, nonstandard synonym
for this flag.
SA_RESTART
Provide behavior compatible with BSD signal semantics by making
certain system calls restartable across signals. This flag
is meaningful only when establishing a signal handler. See
signal(7) for a discussion of system call restarting.
SA_RESTORER
Not intended for application use. This flag is used by C
libraries to indicate that the sa_restorer field contains
the address of a "signal trampoline". See sigreturn(2) for
more details.
SA_SIGINFO (since Linux 2.2)
The signal handler takes three arguments, not one. In this
case, sa_sigaction should be setinstead of sa_handler. This
flag is meaningful only when establishing a signal handler.
signal 重入问题的解决
//通过信号关闭文件描述符以及日志
void xxx_exit(int s){
/*
if(s == SIGINT){
}
else if(s == SIGQUIT){
}
else if(s == SIGTERM){
}
else;
*/
fclose(fp);
closelog();
exit(0);
}
int main(){
struct sigaction sa;
struct sigaction sa_int;
struct sigaction sa_quit;
struct sigaction sa_term;
...
/*
//SIGINT
sa_int.sa_handler = xxx_exit;
sigemptyset(&sa_int.sa_mask);
sigaddset(sa_int.sa_mask, SIGQUIT | SIGTERM);
sa_int.sa_flags = 0;
sigaction(SIGINT, &sa_int, NULL);
//SIGQUIT
sa_quit.sa_handler = xxx_exit;
sigemptyset(&sa_quit.sa_mask);
sigaddset(sa_quit.sa_mask, SIGINT | SIGTERM);
sa_quit.sa_flags = 0;
sigaction(SIGQUIT, &sa_quit, NULL);
//SIGTERM
sa_term.sa_handler = xxx_exit;
sigemptyset(&sa_term.sa_mask);
sigaddset(sa_term.sa_mask, SIGQUIT | SIGTERM);
sa_term.sa_flags = 0;
sigaction(SIGTERM, &sa_term, NULL);
*/
//简化写法
sa.sa_handler = xxx_exit;
sigemptyset(&sa.sa_mask);
sigaddset(sa.sa_mask, SIGINT | SIGQUIT | SIGTERM);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
//signal(SIGINT, xxx_exit);
//signal(SIGQUIT, xxx_exit);
//signal(SIGTERM, xxx_exit);
...
}
使用三参的信号处理函数,处理信号
void (*sa_sigaction)(int, siginfo_t *, void *);
//结构体类型说明
The siginfo_t data type is a structure with the following fields:
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-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 */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
void *si_lower; /* Lower bound when address violation
occurred (since Linux 3.19) */
void *si_upper; /* Upper bound when address violation
occurred (since Linux 3.19) */
int si_pkey; /* Protection key on PTE that caused
fault (since Linux 4.6) */
void *si_call_addr; /* Address of system call instruction
(since Linux 3.5) */
int si_syscall; /* Number of attempted system call
(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */
}
//si_code 信号来源字段
For a regular signal, the following list shows the values which can be placed in si_code for any signal, along with the reason that the signal was generated.
流控处理,修改版(mytbf.c)
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include "mytbf.h"
//存放令牌桶指针的全局变量
static struct mytbf_st* job[MYTBF_MAX];
//时钟信号使用标志位 0为可用 1为不可用
static int inited = 0;
//保存 SIGALRM 信号的原始行为
static struct sigaction alrm_sa_save;
//令牌桶结构体
struct mytbf_st {
//流控速率
int csp;
//令牌最大值 累加上限
int burst;
//令牌大小
int token;
//指针在 job[MYTBF_MAX] 存放位置
int pos;
};
/**
* @name get_free_pos
* @brief 获取令牌指针数组的空缺位置
* @param void
* @return
* pos 空缺位置数组下标 - succeed |
* -1 无空缺位置 - fail
*/
static int get_free_pos(void){
int i = 0;
//遍历数组
for(i = 0; i < MYTBF_MAX; i++){
//如果存在空指针 返回数组下标
if(job[i] == NULL){
return i;
}
}
//无空缺位置 返回-1
return -1;
}
/**
* @name alrm_action
* @brief 时钟信号的处理函数,桶令牌的累加处理
*/
static void alrm_action(int s, siginfo_t *infop, void *unused){
//判断信号来源是否来自内核
if(infop->si_code != SI_KERNEL){
return ;
}
//遍历桶令牌指针数组
for(int i = 0; i < MYTBF_MAX; i++){
//指针非空时对token进行累加计数
if(job[i] != NULL){
job[i]->token += job[i]->cps;
if(job[i]->token > job[i]->burst){
job[i]->token = job[i]->burst;
}
}
}
}
/**
* @name module_unload
* @brief 桶令牌解挂函数,恢复SIGALRM信号的原始行为,并清空时钟信号。
* @param void
* @return void
*/
static void module_unload(void){
//itimer计时器变量参数
struct itimerval itv;
//还原SIGINT信号状态
sigaction(SIGALRM, &alrm_sa_save, NULL);
/* if error */
//清空定时器
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = 0;
itv.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, NULL);
/* if error */
for(int i = 0; i < MYTBF_MAX; i++){
free(job[i]);
job[i] = NULL;
}
}
/**
* @name module_load
* @brief 重新定义SIGALRM信号的行为,保留SIGALRM信号的原始行为,并设置时钟信号。
* @param void
* @return void
*/
static void module_load(void){
//sigaction参数
struct sigaction sa;
//定时器参数
struct itimerval itv;
//定义信号处理函数以及响应机制
sa.sa_sigaction = alrm_action;
sigemptyset(&sa.sa_mask);
//sa_sigaction instead of sa_handler
sa.sa_flags = SA_SIGINFO;
//注册信号行为
sigaction(SIGALRM, &sa, &alrm_sa_save);
/* if error */
//设置定时器
itv.it_interval.tv_sec = 1;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, NULL);
/* if error */
//异常退出处理(钩子函数)
atexit(module_unload);
}
/**
* @name mytbf_init
* @brief 初始化一个令牌桶结构返回一个令牌桶结构指针,
* 并将结构体指针存放在令牌结构体指针数组全局变量中。
*
* @param cps 流控速率
* @param burst token最大值
*
* @return
* NULL - fail |
* mytbf_t类型的结构体指针 - succeed
*/
mytbf_t *mytbf_init(int cps, int burst){
//声明结构体指针
struct mytbf_st *me;
//桶令牌指针位置
int pos;
//判断时钟是否可用
if(!inited){
//初始化时钟信号,和时钟触发机制
module_load();
//将inited置1,时钟信号以初始化,时钟信号不再初始化。
inited = 1;
}
//获取令牌指针数组中的空缺位置
pos = get_free_pos();
//数组已满 无法初始化 返回NULL
if(pos < 0){
return NULL;
}
//申请空间并初始化结构体成员
me = malloc(sizeof(*me));
//分配内存失败 返回空指针
if (me == NULL){
return NULL;
}
me->burst = burst;
me->csp = cps;
me->pos = pos;
me->token = 0;
//将指针存入 指针数组空缺位置中
job[pos] = me;
//返回结构体指针
return me;
}
/**
* @name mytbf_fetchtoken
* @brief 获取token并释放size大小的token
* @param ptr 桶令牌结构体指针
* @param size 需要释放的大小
* @return
* n 成功释放的大小 - succeed |
* -EINVAL errn - faild
*/
int mytbf_fetchtoken(mytbf_t *ptr, int size){
//指针类型强转
struct mytbf_st *me = ptr;
//释放大小
int n;
//size的合法性判断
if(size <= 0){
return -EINVAL;
}
//当token无内容时 等待时钟信号触发
while(me->token <= 0){
pause();
}
//选择 token和size的最小值释放空间
n = min(me->token, size);
me->token -= n;
//返回成功释放空间的大小
return n;
}
/**
* @name mytbf_returntoken
* @brief token归还函数
* @param ptr 桶令牌结构体指针
* @param size 归还的token大小
* @return
* size 成功累加 - succeed |
* -EINVAL errn - faild
*/
int mytbf_returntoken(mytbf_t *ptr, int size){
//指针类型强转
struct mytbf_st *me = ptr;
//size的合法性判断
if(size <= 0){
return -EINVAL;
}
//token增加
me->token += size;
//判断token是否溢出
if(me->token > me->burst){
me->token = me->burst;
}
//成功返回
return size;
}
/**
* @name mytbf_destory
* @brief 销毁令牌结构体并释放空间
* @param ptr 桶令牌结构体指针
* @return
* 默认不会失败 返回 0 - succeed
*/
int mytbf_destory(mytbf_t *ptr){
//指针类型强转
struct mytbf_st *me = ptr;
//释放指针指向的结构体
free(ptr);
//将待删除位置指针指控
job[me->pos] = NULL;
//成功返回
return 0;
}
9 实时信号
如果一个进程同时收到标准信号和实时信号,优先响应标准信号。
实时信号存放文件 /usr/include/x86_64-linux-gnu/bits/signum.h
//signum.h
//这两个是未定义信号留给用户的
#undef SIGUSR1
#define SIGUSR1 10
#undef SIGUSR2
#define SIGUSR2 12
//signal.h
#define SIGRTMIN (__libc_current_sigrtmin ())
#define SIGRTMAX (__libc_current_sigrtmax ())
实时信号不发生丢失需要排队,其他用法与标准信号完全一致。
查看实时信号排队上限 ulimit -a
wangs7_@WangQ7:/mnt/c/Users/MrWangS7$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7823 #实时信号排队上限
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7823
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited