1.信号机制也需要信号源:
用户:用户通过终端键入特殊字符(ctrl+c)。进程:运行kill命令或者kill()系统调用。系统:系统异常(浮点异常,访问非法内存),系统状态发生变化
信号机制的过程无非在于信号的发送,接受和处理;
一个进程给其他进程发送信号的
API是kill()系统调用
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid, int sig);
pid 指定目标进程。pid>0:信号发送给PID为pid的进程。。pid=0:信号发送给本进程组内的其他进程。。pid=-1:信号发送给除init进程外的所有进程;pid<-1:信号发送给组ID为-pid的进程组中的所有成员
sig:取值为0则kill()函数不发生任何信号,可用来测试目标进程/进程组是否存在。
返回值,成功就返回0,失败返回-1,并设置errno:包括EINVAL(无效的信号)EIPERM(该进程没有权限发送信号给任何一个目标进程)ESRCH(目标进程或进程组不存在)
2.信号的处理:
signal()系统调用;可以为一个信号设置处理函数
#include<signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,
sighandler_t handler);
signum:指出要捕获的信号
handler是一个sighandler_t类型的函数指针,指向的是signum信号的处理函数
执行成功返回一个sighandler_t类型的函数指针,可以是前一次调用signal时传入的函数指针,或者是signum默认信号处理函数的指针
#define SIG_DFL ((__sighandler_t)0) /* 默认处理 */
#define SIG_IGN ((__sighandler_t)1) /* 忽略 */
#define SIG_ERR ((__sighandler_t)-1) /* 出错,用于返回的 */
3.sigaction()系统调用;
设置信号处理函数更健壮的系统调用
#incldue<signal.h>
int sigacton(int signum,const struct sigaction* act,
struct sigaction* oldact);
signum:指出要捕获的信号
act:指定新的信号处理方式
oldact:输出型参数,用于返回之前的信号处理方式
struct sigaction {
void (*sa_handler)(int);
/* 信号处理函数 */
void (*sa_sigaction)(int, siginfo_t *, void *);
/* 另外一个信号处理,默认使用上一个函数,
若要使用本函数需要在sa_flags设置 */
sigset_t sa_mask; /* 设置进程的信号掩码 */
int sa_flags; /* 设置程序收到信号时的行为 */
void (*sa_restorer)(void); /* 不使用 */
};
sa_mask是指在进程原有的信号掩码的基础上增加掩码,用于设置那些信号不能发送给本地进程sigset_t表示信号集类型sigset_t实际上是一个长整型数组,数组的每一元素的每一位表示一个信号(跟select()系统调用中的fd_set类似)。下面函数用于设置、修改、删除和查询sigset_t信号集:
#include<signal.h>
int sigemptyset(sigset_t *set);清空
int sigfillset(sigset_t* set);
在信号集中设置所有信号,若参数是等于
上面的sa_mask则屏蔽所有信号
int sigaddset(sigset_t*set,int signum);
将signum信号添加到信号集set
int sigdelset(sigset_t*set, int signum);
将signum从set删除
int sigismember(const sigset_t*set,int signum);
测试signum信号是否在信号集中
sa_flags设置收到信号时的行为:SA_NOCLDSTOP: signum为SIGCHLD,子进程暂停时不产生SIGCHLD信号
SA_NOCLDWAIT: signum为SIGCHLD,子进程结束时不产生僵尸进程
SA_SIGINFO: 使用sa_sigaction作为信号处理函数(默认是sa_handler)
SA_ONSTACK/SA_STACK: 重新调用被该信号终止的系统调用
SA_NODEFER/SA_NOMASK: 当程序接收到信号并进入对应信号处理函数后不屏蔽该信号。进程在执行信号处理函数时默认情况下是不再接收
同种信号,否则会引起一些竟态条件
SA_RESETHAND/SA_ONESHOT: 信号处理函数执行完毕后恢复信号的默认处理方式
SA_INTERRUPT: 中断系统调用
进程的信号掩码:sigpromask()也可以设置/查看进程的信号掩码:
#include<signal.h>
int sigprocmask(int how,const sigset_t* set,
sigset_t* oldset);
how指定设置进程信号掩码的方式,SIG_BLOCK: 新的进程信号掩码是当前值和set执行的信号集的并集
SIG_UNBLOSK: 新的进程信号掩码是当前值和~set信号集的交集,即set指定的信号集不被屏蔽
SIG_SETMASK: 直接将进程信号掩码设置为setset设置新的信号掩码
oldset是输出型函数,返回原来的信号掩码(不为NULL的话),若set为NULL则进程信号掩码不变
函数成功返回0,
本进程设置进程信号掩码(来屏蔽信号)后,若其它进程发来被屏蔽的信号,本进程将不能被接收该信号,而操作系统会将该信号设置为本进程的一个被挂起的信号。当程序员取消对本进程挂起的信号的屏蔽后,则该信号将立即被进程接收到。获取当前进程被挂起的信号集的系统调用是sigpending
int sigpending(sigset_t *set);
(1)set参数是输出型参数,用于返回被挂起的进程。需要注意,进程即使多次接收到同一个被挂起的信号,sigpending()系统调用也只能反映一次,且当程序员再次使用sigprocmask()使能该挂起的信号时,该信号的处理函数也只能被触发一次。
(2)函数执行成功返回0,失败返回-1并设置errno。
3.统一事件源:
将信号的主要处理逻辑放在程序的主循环中实现,当信号处理函数被触发时,它只负责简单地通知主循环程序接收到信号,并将信号值传递给主循环,主循环再根据接收到的信号值执行目标信号对应的逻辑代码。
信号处理函数使用管道(无名管道即可,因为处理函数和主循环函数处于同一进程空间)和主循环交互,即信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值。主循环如何知道管道上有数据可读,这就通过之前学的IO复用系统调用来监听管道的读端文件描述符上的可读事件。这样,信号事件和其他的IO事件已被被IO复用处理,即统一事件源
4.中断系统调用:
如果程序在执行处于阻塞状态的系统调用时收到信号,并且我们为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并设置errno为EINTR,使用sigaction函数为信号设置SA_RESTART标志以自动重启被该信号中断的系统调用
对于默认行为是暂停进程的信号,如果我们没有为他们设置信号处理函数,则他们也可以中断某些系统调用如(connect,epoll_wait)
SIGHUP:
当挂起进程的控制终端时,SIGHUP触发,对于没有控制终端的网络后台程序而言,通常使用SIGHUP信号来强制服务器重读配置文件。
SIGPIPE:
往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号,我们要在代码中捕获并处理该信号,或者忽略它,因为程序收到SIGPIPEE信号的默认行为是结束进程,设置默认errno为EPIPE;
SIGURG:
内核通知应用程序带外数据到达有两种办法:I/O复用技术的select,还有就是SIGURG信号:
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#include<fcntl.h>
#define BUF_SIZE 1024
static int connfd;
void sig_urg(int sig)
{
int save_errno = errno;
char buffer[BUF_SIZE];
memset(buffer,'\0',BUF_SIZE);
int ret = recv(connfd,buffer,BUFFER_SIZE-1,MAG_OOB);
printf("got %d bytes of oob data
'%s'\n",ret,buffer);
errno = save_errno;
}
void addsig(int sig,void(*sig_handler)(int))
{
struct sigaction sa;
memset(&sa,'\0',sizeof(sa);
sa.sa_handler = sig_handler;
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
assert(sigaction(sig,&sa,NULL)!= -1);
}
int main(int argc,char* argv[])
{
if(argc <= 2)
{
printf("")
}
const char * ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
bzero(&address,sizeof(address);
address.sin_family = AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port = htons(port);
int sock = socket(PF_INET,SOCK_ATREAM,0);
assert(sock >= 0);
int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
asssert(ret != -1);
ret = listen(sock,5);
assert(ret != -1);
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
connfd = accept(sock,(struct sockaddr*)&client,
&client_addrlength);
if(connfd < 0)
{
printf("errno is:%d\n",errno);
}
else
{
addsig(SIGURG,sig_urg);
在使用SIGURG之前必须设置socket的宿主进程或组
fcntl(connfd,F_SETOWN,getpid());
char buffer[BUFFER_SIZE];
while(1)
{
memset(buffer,'\0',BUF_SIZE);
ret = recv(connfd,buffer,BUF_SIZE-1,0);
if(ret <= 0)
{
break;
}
printf("got %d bytes of normal
data '%s'\n",ret,buffer);
}
close(connfd);
}
close(sock);
return 0;
}