1.Linux中的信号(有32个) 信号会中断一些函数的阻塞
https://zhidao.baidu.com/question/1766690354480323100.html
#define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGIO 29 #define SIGPOLL SIGIO /* #define SIGLOST 29 */ #define SIGPWR 30 #define SIGSYS 31 /* signal 31 is no longer "unused", but the SIGUNUSED macro remains for backwards compatibility */ #define SIGUNUSED 31 /* These should not be considered constants from userland. */ #define SIGRTMIN 32 #define SIGRTMAX _NSIG
SIGCHLD的更多细节 子进程状态变更了,例如停止、继续、退出等,都会发送这个信号通知父进程。而父进程就能通过 wait/waitpid 来获悉这些状态了 https://segmentfault.com/a/1190000015060304
信号因为是异步机制(可能会信号叠加)会造成系统的不稳定,所以应尽量减少信号(应用于一些突发事件)
如:Linux终端的Ctrl+C(强制结束进程)这样的信号.
2.如何发起异步操作
2.1(kill -signum pid) kill其实是发送信号的宏观指令 如kill -9(SIGKILL
) pid
signal()把信号绑定指定函数
signal的大概使用: signal(signum(SIGINT) , myfunc) , 当发出SIGINT信号时执行myfunc函数 https://www.runoob.com/cprogramming/c-function-signal.html
int kill(pid,signum);
pid>0 发给pid进程
pid=0 发给当前进程组的所有进程
pid=-1 发送给所有进程
pid<0 发送给|PID|所对应的组上
pid = getpid()获得当前进程的ID gpid = getgpid():获得当前进程的组ID
2.2自举信号
自己给自己发送信号 , 也就是给程序本身 raise 一个信号,然后执行信号所绑定的函数
int raise(int sig); == int kill(getpid , signum)
2.3定时函数
usigned int alarm(unsigned int seconds) 函数会在指定seconds之后收到SIGALRM信号
如: 两秒后打印一个printf signal(SIGALRM , printf);
alarm(2);
alarm是一次性的 大概实现是在内核的一个 jiffies(程序开始时从0每隔10毫秒+1的一个数) 上累加的,如一个程序开始执行1秒后触发, 那就是 jiffies+100 触发. 因为一个程序只有一个jiffise,所以多次使用alarm时后面的alarm会覆盖前面的
ualarm是多次的 useconds_t ualarm(useconds_t usecs , useconds_t interval);
第一个参数是第一次产生的时间 , 第二个参数是间隔时间
因为alarm和ualarm(不推荐使用)时间不准确 所以有了setitimer(推荐使用) 精确的定时器
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval new_value, struct itimerval *old_value)
详细 https://blog.csdn.net/fjt19900921/article/details/8072903
struct itimerval { struct timeval it_interval; /* next value 间隔时间*/ struct timeval it_value; /* current value 第一次的时间*/ }; struct timeval { time_t tv_sec; /* seconds 秒*/ suseconds_t tv_usec; /* microseconds 微秒*/ };
Linux会提供三个定时器,每个定时器返回的信号都是不同的 ITIMER_REAL ITIMER_VIRTUAL ITIMER_PROF ,一般没什么区别,使用第一个就行了
3.安装和捕获信号
3.1忽略信号
signal(signum , SIG_IGN); SIG_IGN = 接收到信号,但是作空处理和屏蔽信号(让信号在屏蔽队列等待,何时关闭屏蔽,何时才接收信号)有区别
3.2自定义捕捉函数
signal(signum , handler);
SIGKILL 和 SIGSTOP 不能被捕捉
3.3系统默认信号函数
如 ctrl+c 会发送SIGINT 执行系统的函数结束进程
3.4因为signal()局限小不推荐使用 , 所以有了sigaction对象
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); //上面两个选其一使用 ,选哪个取决于flag的状态 sigset_t sa_mask; //一般信号屏蔽时用的(把你想屏蔽的信号加入到sa_mask集合, 再利用信号屏蔽函数进行操作 看3.5) int sa_flags; //会影响信号接受特殊标志 void (*sa_restorer)(void); //不做使用NULL.有必要时传入个sigaction对象,用作备份方便恢复,sigaction()会把旧的sigaction对象和所绑定的设置传出 }; sa_flags = 0 调用第一个函数
sa_flags = SA_INTERRUPT:由此信号中断的系统调用不会自动重启动(针对sigaction的XSI默认处理方式不会自动重启)
SA_RESTART: 由此信号中断的系统调用会自动重启。
SA_NOCLDSTOP:一般当进程终止或停止时都会产生SIGCHLD信号,但若对SIGCHLD信号设置了该标志,当子进程停止时不产生此信号。当子进程终止时才产生此信号。
SA_NODEFER:
SA_RESETHAND:
这两个标志对应早期的不可靠信号机制,除非明确要求使用早期的不可靠信号,否则不应该使用。这里也不多做介绍
SA_SIGINFO:当我们没有使用 SA_SIGINFO 标志时,调用的是 sa_handler指定的信号处理函数
我们使用了 SA_SIGINFO标志 然后使用了 sa_sigaction字段表示的处理函数。
sigset_t 信号集合 siginfo_t { int si_signo; /* Signal number */ //信号编号 int si_errno; /* An errno value */ //如果为非0值则错误代码于之关联 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 */ //发送信号的进程ID uid_t si_uid; /* Real user ID of sending process */ //发送信号的用户ID 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) */ }
3.5信号屏蔽集合的操作函数
int sigemptyset(sigset_t* set); 清空信号集合
int sigfillset(sigset_t* set); 填满信号集合(把所有信号都放进去)
int sigaddset(sigset_t* set, int signum); 往set集合里追加signum
int sigdelset(sigset_t* set, int signum); 把signum从set中删除
int sigismember(const sigset_t *set, int signum); 测试set里是否有signum
#include<stdio.h> #include<stdlib.h> #include<signal.h> void myHandler(int sig); int main(int argc,char *argv[]) { struct sigaction act, oact; act.sa_handler = myHandler; sigemptyset(&act.sa_mask); /*initial. to empty mask*/ act.sa_flags = 0; sigaction(SIGUSR1, &act, &oact); while (1) { printf("Hello world. "); pause(); } } void myHandler(int sig) { printf("I got signal: %d. ", sig); }
4.sigqueue() 发送信号函数 https://www.cnblogs.com/mickole/p/3191804.html
之前学过kill,raise,alarm,abort等功能稍简单的信号发送函数,现在我们学习一种新的功能比较强大的信号发送函数sigqueue.
sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
int sigqueue(pid_t pid, int sig, const union sigval val) 调用成功返回 0;否则,返回 -1
sigqueue的第一个参数是指定接收信号的进程ID,
第二个参数确定即将发送的信号,
第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
typedef union sigval { //可以存放4字节的数据 指针只能在同一进程传递 https://www.cnblogs.com/mickole/p/3191804.html
int sival_int;
void *sival_ptr;
}sigval_t;
si_value :系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数( void (*sa_sigaction)(int, siginfo_t *, void *))的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
收发信号的具体实现:
#include<stdio.h> #include<unistd.h> #include<signal.h> #include<string.h> //#include<sys/siginfo.h> void handler(int,siginfo_t *,void *); int main(void) { struct sigaction act; act.sa_sigaction=handler; sigemptyset(&act.sa_mask); act.sa_flags=SA_SIGINFO; if(sigaction(SIGINT,&act,NULL)<0) { printf("error"); exit(0); } for(;;) pause(); return 0; } void handler(int sig,siginfo_t * info,void *ctx) { printf("recv a sid=%d data=%d data=%d ",sig,info->si_value.sival_int,info->si_int); }
#include<stdio.h> #include<signal.h> #include<unistd.h> #include<string.h> int main(int argc,char *argv[]) { if(argc!=2) { printf("arguments error!"); exit(0); } pid_t pid=atoi(argv[1]);//将进程号转化为整数 union sigval v; v.sival_int=100; //这儿只是利用SIGINT来发送数据, //任何信号都行,只需要发送端与接收端规定一致即可 sigqueue(pid,SIGINT,v); return 0; }
5.设置信号屏蔽集
利用信号屏蔽集合的操作函数,设置好信号屏蔽集, 再使用sigprocmask()进行添加或删除
int sigprocmask(int how, const sigset_t set, sigset_t *oldset);
how = SIG_BLOCK 将设置好的信号屏蔽集set加入到当前进程的屏蔽集里
SIG_UNBLOCK 将设置好的信号屏蔽集set从当前进程的屏蔽集里删除
SIG_SETMASK 将设置好的信号屏蔽集set设置为当前进程的屏蔽集
第三个参数作备份用的
6.未决信号 因为未捕获或屏蔽的信号称为未决信号
屏蔽信号不是不接收该信号了,而是接收信号后把该信号放入信号屏蔽的队列里不做处理, 这些信号称为未决信号 , 队列里的信号不会重复,因为信号会被覆盖.
忽略的信号不会加入到信号屏蔽的队列里
int sigpending(sigset_t *set); 查询有多少未决信号
7.阻塞的信号函数
int sigsuspend(const sigset_t *mask); 阻塞进程(收到mask里的信号继续阻塞),等待 mask 之外的信号唤醒
阻塞信号的实现 https://blog.csdn.net/weiyuefei/article/details/72457739
屏蔽信号会收到信号但是不做处理,他会收到未决信号队列里, 而阻塞信号会收到也会处理,但是不会返回
8.信号通信
信号可以中断函数的阻塞 , 如: accept被信号打断后的处理
ACCEPT:
confd = accept(srvfd , (struct sockaddr *)&tcilent , &tclientaddlen) if(-1 == confd) { if(errno == EINTR) { goto ACCEPT; } else { return -1; } }
因为信号函数不能做时间长的(信号处理函数时间过长可能会出现被其他信号中断的情况) , 容易死锁的 , 线程不安全的操作 ,
所以有了把信号转变成事件的操作: 信号处理函数通过管道pipe(pfd) , wirte(pfd[0] , signum/void* ptr , size),往管道写入数据 ,
管道的另一端会往 fd 写入数据, 然后发生event , 这样就可以把信号转换成事件了, 再通过epoll处理, 高并发服务器一般都是这样写的
#include<stdio.h> #include<stdlib.h> #include<signal.h> #include<unistd.h> #include<sys/time.h> #include<error.h> #include<arpa/inet.h> #include<sys/epoll.h> #include <fcntl.h> #include<errno.h> #if 1 /***************************************************************************** 函 数 名 : MyHandler 功能描述 : 信号处理函数 输入参数 : int sig , struct siginfo_t *info , void *ctx 输出参数 : 无 返 回 值 : void 调用函数 : 被调函数 : 修改历史 : 1.日 期 : 2019年11月8日 星期五 作 者 : ljf 修改内容 : 新生成函数 *****************************************************************************/ void MyHandler( int sig , struct siginfo_t *info , int *pipefd) { char *buff = "hello world "; size_t len = sizeof(buff); write(pipefd[1], buff, len); } /***************************************************************************** 函 数 名 : readn 功能描述 : 不被信号所打断的完整读取 输入参数 : int fd , void* buf , size_t len 输出参数 : 无 返 回 值 : ssize_t 调用函数 : 被调函数 : 修改历史 : 1.日 期 : 2019年11月8日 星期五 作 者 : ljf 修改内容 : 新生成函数 *****************************************************************************/ ssize_t readn(int fd , void* buf , size_t len) { size_t nleft = len; ssize_t nread; char* pbuf = buf; while ( nleft > 0 ) { if ( (nread = read(fd , pbuf , nleft)) == -1 ) { if(errno == EINTR) //如果被信号所中断 nread = 0; //continue 因为nread=0 所以等同于重新开始下一次循环 else return -1; }else if ( nread == 0 ) { break; } nleft -= nread; //实际大小减去已读取的大小 等于未读取的大小 pbuf += nread; //从未完的位置开始继续读取 } return (len - nleft); } int main(int argc, char *argv[]) { struct sigaction act , oact; int pipe_fd[2] = {0,}; int epoll_fd; pipe(pipe_fd); act.sa_handler = MyHandler; //捕捉信号和处理 sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; if ( sigaction(SIGUSR1 , &act , pipe_fd) < 0 ) { perror("sigaction"); exit(1); } epoll_fd = epoll_create(256); if ( epoll_fd < 0 ) { perror("epoll_create1"); exit(2); } struct epoll_event ep_ev; ep_ev.events = EPOLLIN; ep_ev.data.fd = pipe_fd[0]; if ( epoll_ctl(epoll_fd , EPOLL_CTL_ADD , pipe_fd[0] , &ep_ev) < 0 ) { perror("epoll_ctl"); exit(3); } struct epoll_event ready_event[128]; //存放就绪事件 int maxnum = 128; //就绪事件最大数 int timeout = 3000; //超时时间10s int ret = 0; //接收就绪事件的数量 while(1) { ep_wait: switch ( ret = epoll_wait(epoll_fd , ready_event , maxnum , timeout) ) { case -1 : { if ( errno == EINTR ) { goto ep_wait; } else { perror("epoll_wait"); exit(4); } }break; case 0 : fputs("time out... ", stdout); break; default: { int i=0; char buf[1024]; for ( ; i< ret ; i++ ) { if ( ready_event[i].events & EPOLLIN ) { memset(buf,'