信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。Linux 信号可由如下条件产生:

  • 对于前台进程,用户可以通过输人特殊的终端字符来给它发送信号。比如输入Ctrl+C通常会给进程发送一个中断信号。
  • 系统异常。比如浮点异常和非法内存段访问。
  • 系统状态变化。比如alarm定时器到期将引起SIGALRM信号。
  • 运行kill命令或调用kill 函数。
  • 服务器程序必须处理(或至少忽略)一些常见的信号,以免异常终止。

发送信号

Linux下,一个进程给其他进程发送信号的API是kill函数。其定义如下:

#include <sys/types.h>
#include <signal.h>/* Send signal SIG to process number PID.  If PID is zero,send SIG to all processes in the current process's process group.If PID is < -1, send SIG to all processes in process group - PID.  */
#ifdef __USE_POSIX
extern int kill (__pid_t __pid, int __sig) __THROW;
#endif /* Use POSIX.  */

该函数把信号sig发送给目标进程;目标进程由pid参数指定,其可能的取值及含义如:

Linux定义的信号值都大于0,如果sig取值为0,则kill函数不发送任何信号。但将sig设置为0可以用来检测目标进程或进程组是否存在,因为检查工作总是在信号发送之前就执行。不过这种检测方式是不可靠的。一方面由于进程PID的回绕,可能导致被检测的PID不。是我们期望的进程的PID;另一方面,这种检测方法不是原子操作。

该函数成功时返回0,失败则返回-1并设置errmo。几种可能的errno 如表所示。

信号处理

目标进程在收到信号时,需要定义一个接收函数来处理之。信号处理函数的原型如下:

/* Type of a signal handler.  */
typedef void (*__sighandler_t) (int);

信号处理函数只带有一个整型参数,该参数用来指示信号类型。信号处理函数应该是可重入的(就是多次调用,执行的结果是一致的),否则很容易引发一些竞态条件(前后两次运行同一个程序,出现的结果不同。)。所以在信号处理函数中严禁调用一些不安全的函数。

除了用户自定义信号处理函数外,bits/signum.h头文件中还定义了信号的两种其他处理SIG_IGNSIG_DEL

/* Fake signal functions.  */#define SIG_ERR  ((__sighandler_t) -1)  /* Error return.  */
#define SIG_DFL  ((__sighandler_t)  0)  /* Default action.  */
#define SIG_IGN  ((__sighandler_t)  1)  /* Ignore signal.  */#ifdef __USE_XOPEN
# define SIG_HOLD ((__sighandler_t) 2)  /* Add signal to hold mask.  */
#endif

SIG_IGN表示忽略目标信号,SIG_DFL表示使用信号的默认处理方式。信号的默认处理方式有如下几种:结束进程(Term)、 忽略信号(Ign)、 结束进程并生成核心转储文件(Core)、暂停进程(Stop), 以及继续进程(Cont)。

Linux信号

中断系统调用

如果程序在执行处于阻塞状态的系统调用时接收到信号,并且我们为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并且errno被设置为EINTR。我们可以使用sigaction函数为信号设置SA_RESTART标志以自动重启被该信号中断的系统调用。

对于默认行为是暂停进程的信号(比如SIGSTOP、SIGTTIN),如果我们没有为它们设置信号处理函数,则它们也可以中断某些系统调用( 比如connect、epoll_wait)。 POSIX没有规定这种行为,这是Linux独有的。

信号函数signal

要为一个信号设置处理函数,可以使用下面的signal系统调用:

#include <signal.h>//signal系统调用出错时返回SIG_ERR,并设置ermo。/* Set the handler for the signal SIG to HANDLER, returning the oldhandler, or SIG_ERR on error.By default `signal' has the BSD semantic.  */
#ifdef __USE_MISC
extern __sighandler_t signal (int __sig, __sighandler_t __handler)__THROW;
#else
/* Make sure the used `signal' implementation is the SVID version. */
# ifdef __REDIRECT_NTH
extern __sighandler_t __REDIRECT_NTH (signal,(int __sig, __sighandler_t __handler),__sysv_signal);
# else
#  define signal __sysv_signal
# endif
#endif

sig参数指出要捕获的信号类型。__handler 参数是。__sighandler_t 类型的函数指针,用于指
定信号sig的处理函数。

signal函数成功时返回一个函数指针,该函数指针的类型也是__sighandler__t。这个返回值是前一次调用signal函数时传人的函数指针,或者是信号sig对应的默认处理函数指针SIG_DEF (如果是第一次调用signal的话)。

信号函数sigaction

设置信号处理函数的更健壮的接口是如下的系统调用:

#include <signal.h>/* Get and/or set the action for signal SIG.  */
extern int sigaction (int __sig, const struct sigaction *__restrict __act,struct sigaction *__restrict __oact) __THROW;
//sigaction成功是返回0,失败返回-1,并不设置ermo。

sig参数指出要捕获的信号类型,act参数指定新的信号处理方式,oact参数则输出信号先前的处理方式(如果不为NULL的话)。act 和oact都是sigaction结构体类型的指针,sigaction结构体描述了信号处理的细节,其定义如下:

/* Structure describing the action to be taken when a signal arrives.  */
struct sigaction{/* Signal handler.  */
#if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDEDunion{/* Used if SA_SIGINFO is not set.  */__sighandler_t sa_handler;/* Used if SA_SIGINFO is set.  */void (*sa_sigaction) (int, siginfo_t *, void *);}__sigaction_handler;
# define sa_handler __sigaction_handler.sa_handler
# define sa_sigaction   __sigaction_handler.sa_sigaction
#else__sighandler_t sa_handler;
#endif/* Additional set of signals to be blocked.  */__sigset_t sa_mask;/* Special flags.  */int sa_flags;/* Restore handler.  */void (*sa_restorer) (void);};

该结构体中的sa_hander成员指定信号处理函数。sa__mask成员设置进程的信号掩码(确切地说是在进程原有信号掩码的基础上增加信号掩码),以指定哪些信号不能发送给本进程。sa_mask是信号集sigset_t (__sigset__t 的同义词)类型,该类型指定一组信号。sa_flags 成员用于设置程序收到信号时的行为,其可选值如表所示:

sa_restorer成员已经过时,最好不要使用。

信号集函数

Linux使用数据结构__sigset_t来表示一组信号,其定义如下:

#include <bits/types/__sigset_t.h>#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;

由该定义可见,sigset_t实际上是一个长整型数组,数组的每个元素的每个位表示一个信号。这种定义方式和文件描述符集fd_set 类似。Linux 提供了如下一-组函数来设置、修改、删除和查询信号集:

#include <signal .h>
int sigemptyset (sigset_t*_set)/*清空信号集*/
int sigfllset (sigset_t*_set)/*在信号集中设置所有信号*/
int sigaddset (sigset_t*_set, int_signo)/*将信号_signo 添加至信号集中*/
int sigdelset (sigset_ t*_set, int_signo)/*将信号_signo从信号集中删除*/
int sigismember (_const sigset__t*__set, int__signo) /*测试_signo是否在信号集中 */

进程信号掩码

前文提到,我们可以利用sigaction结构体的sa_mask成员来设置进程的信号掩码。此
外,如下函数也可以用于设置或查看进程的信号掩码:

#include <signal.h>
/* Get and/or change the set of blocked signals.  */
extern int sigprocmask (int __how, const sigset_t *__restrict __set,sigset_t *__restrict __oset) __THROW;
//sigprocmask成功时返回0,失败则返回-1并设置errno。

_set 参数指定新的信号掩码,_oset 参数则输出原来的信号掩码(如果不为NULL的话)。如果_set参数不为NULL,则__how参数指定设置进程信号掩码的方式,其可选值如表

如果__set 为NULL,则进程信号掩码不变,此时我们仍然可以利用_oset 参数来获得进
程当前的信号掩码。

被挂起的信号

设置进程信号掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被 屏蔽的信号,则操作系统将该信号设置为进程的–个被挂起的信号。如果我们取消对被挂起信号的屏蔽,则它能立即被进程接收到。如下函数可以获得进程当前被挂起的信号集:

/* Put in SET all signals that are blocked and waiting to be delivered.  */
extern int sigpending (sigset_t *__set) __THROW __nonnull ((1));
//sigpending成功时返回0,失败时返回-1并设置ermo。

set参数用于保存被挂起的信号集。显然,进程即使多次接收到同一个被挂起的信号,sigpending丽数也只能反映一次。 并且,当我们再次使用sigprocmask使能该挂起的信号时,该信号的处理函数也只被触发一次。

统一事件源

信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。很显然,信号处理函数需要尽可能快地执行完毕,以确保该信号不被屏蔽(前面提到过,为了避免一些竞态条件, 信号在处理期间,系统不会再次触发它)太久。一种典型的解决方案是:把信号的主要处理逻辑放到程序的主循环中,当信号处理函数被触发时,它只是简单地通知主循环程序接收到信号,并把信号值传递给主循环,主循环再根据接收到的信号值执行目标信号对应的逻辑代码。信号处理函数通常使用管道来将信号“传递”给主循环:信号处理函数往管道的写端写人信号值,主循环则从管道的读端读出该信号值。那么主循环怎么知道管道上何时有数据可读呢?这很简单,我们只需要使用I/O复用系统调用来监听管道的读端文件描述符上的可读事件。如此-来,信号事件就能和其他I/O事件一样被处理,即统一事件源。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>#define MAX_EVENT_NUMBER 1024
static int pipefd[2];int setnonblocking(int fd)
{int old_option = fcntl(fd, F_GETFL);int new_option = old_option | O_NONBLOCK;fcntl(fd, F_SETFL, new_option);return old_option;
}void addfd(int epollfd, int fd)
{epoll_event event;event.data.fd = fd;event.events = EPOLLIN | EPOLLET;epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);setnonblocking(fd);
}void sig_handler(int sig)
{int save_errno = errno;int msg = sig;send(pipefd[1], (char *)&msg, 1, 0);errno = save_errno;
}void addsig(int sig)
{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("usage: %s ip_address port_number\n", basename(argv[0]));return 1;}const char *ip = argv[1];int port = atoi(argv[2]);int ret = 0;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 listenfd = socket(PF_INET, SOCK_STREAM, 0);assert(listenfd >= 0);//int nReuseAddr = 1;//setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &nReuseAddr, sizeof( nReuseAddr ) );ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));if (ret == -1){printf("errno is %d\n", errno);return 1;}//assert( ret != -1 );ret = listen(listenfd, 5);assert(ret != -1);epoll_event events[MAX_EVENT_NUMBER];int epollfd = epoll_create(5);assert(epollfd != -1);addfd(epollfd, listenfd);ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);assert(ret != -1);setnonblocking(pipefd[1]);addfd(epollfd, pipefd[0]);// add all the interesting signals hereaddsig(SIGHUP);addsig(SIGCHLD);addsig(SIGTERM);addsig(SIGINT);bool stop_server = false;while (!stop_server){int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);if ((number < 0) && (errno != EINTR)){printf("epoll failure\n");break;}for (int i = 0; i < number; i++){int sockfd = events[i].data.fd;if (sockfd == listenfd){struct sockaddr_in client_address;socklen_t client_addrlength = sizeof(client_address);int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);addfd(epollfd, connfd);}else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)){int sig;char signals[1024];ret = recv(pipefd[0], signals, sizeof(signals), 0);if (ret == -1){continue;}else if (ret == 0){continue;}else{for (int i = 0; i < ret; ++i){//printf( "I caugh the signal %d\n", signals[i] );switch (signals[i]){case SIGCHLD:case SIGHUP:{continue;}case SIGTERM:case SIGINT:{stop_server = true;}}}}}else{}}}printf("close fds\n");close(listenfd);close(pipefd[1]);close(pipefd[0]);return 0;
}

外带数据信号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 1024static 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, BUF_SIZE - 1, MSG_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("usage: %s ip_address port_number\n", basename(argv[0]));return 1;}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_STREAM, 0);assert(sock >= 0);int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));assert(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);fcntl(connfd, F_SETOWN, getpid());char buffer[BUF_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;
}

高性能服务器编程-信号相关推荐

  1. 《Linux高性能服务器编程》学习笔记

    <Linux高性能服务器编程>学习笔记 Linux高性能服务器编程 TCP/IP协议族 TCP/IP协议族体系结构以及主要协议 数据链路层 网络层 传输层 应用层 封装 分用 测试网络 A ...

  2. Linux 高性能服务器编程——多线程编程

    问题聚焦:     在简单地介绍线程的基本知识之后,主要讨论三个方面的内容:     1 创建线程和结束线程:     2 读取和设置线程属性:     3 线程同步方式:POSIX信号量,互斥锁和条 ...

  3. Linux高性能服务器编程——书籍阅读笔记

    目录 前言 正文 第一章 1. 零拷贝函数 2. TCP/IP协议族 3. OSPF 4. ARP协议 5. RARP 6. ICMP协议 7. TCP协议 8. UDP协议 9. 封装 第四章 TC ...

  4. 《Linux高性能服务器编程》——导读

    前 言 为什么要写这本书 目前国内计算机书籍的一个明显弊病就是内容宽泛而空洞.很多书籍长篇大论,恨不得囊括所有最新的技术,但连一个最基本的技术细节也无法解释清楚.有些书籍给读者展现的是网络上随处可见的 ...

  5. 【Todo】【读书笔记】Linux高性能服务器编程

    在读 /Users/baidu/Documents/Data/Interview/服务器-检索端/<Linux高性能服务器编程.pdf> 其实之前读过,要面试了,需要温习. P260 So ...

  6. linux高性能服务器编程书本总结

    目录 目录分析 第一篇从 1-4章节主要是介绍 计算机网络基础知识和 TCP/IP模型 第二篇 核心篇 5 章到 15 章节 5-6章节 主要介绍 套接字编程API的使用和介绍 7章 是linux 服 ...

  7. linux高性能服务器编程第八章(高性能服务器程序框架)

    C/S模型 传统C/S,一端作为客户端,一端作为服务器,这里不做多介绍. P2P模型 peer 2 peer ,每台机器使用服务的同时也提供服务,通俗的讲,没有绝对客户端和服务端的概念,当下云计算的模 ...

  8. Linux高性能服务器编程 第5章 Linux网络编程基础API

    5.1 socket 地址 API 现代CPU的累加器一次都能装载(至少)4 字节(这里考虑32位机,下同),即一个整 数.那么这4 字节在内存中排列的顺序将影响它被累加器装载成的整数的值.这就是字节 ...

  9. 高性能服务器编程代码清单9-3,JRockit JVM优化清单/调优指南

    摘要 本文的目的是以清单的方式提供BEA JRockit JVM的调优信息.从深奥的命令行选项到迭代性能测试,本文涵盖了许多方面.大部分数据都是我与用户合作过程中收集的.您要是也有什么技巧的话,请告诉 ...

最新文章

  1. Mac上搭建直播服务器Nginx+rtmp
  2. saltstack mysql_saltstack mysql returner
  3. SQLErrorCodeSQLExceptionTranslator 使用以下的匹配规则
  4. 高仿真的类-DefaultListableBeanFactory
  5. 借钱合同有哪些陷阱?
  6. shell变量/环境变量和set/env/export用法_转
  7. ruby array_Ruby中带有示例的Array.select方法
  8. kong 启动报错 could not retrieve server_version: timeout
  9. @Autowired报错的4种解决方案和原因分析!
  10. Ubuntu 上寻找依赖库(.so)的方法
  11. partial关键字的含义和使用
  12. Elicpse使用技巧-打开选中文件文件夹或者包的当前目录
  13. kotlin 反射java类_Android Kotlin的Class、反射、泛型
  14. excel中添加图片的代码
  15. 汉王数据导入java环境,怎样把u盆内容导入汉王门禁考勤管理软件
  16. tp交换机管理页面_tplink交换机设置步骤使用方法
  17. 守夜人誓言+考研誓言
  18. ZCC9628单向全波无刷马达驱动芯片替代AM7228
  19. [PED08]Self-paced Clustering Ensemble自步聚类集成论文笔记
  20. LeetCode简单题643.子数组的最大平均数I

热门文章

  1. 如何用计算机装B,计算机专业装逼经验(转)
  2. 洞悉物联网发展1000问之为什么新零售不仅仅是无人零售?
  3. 【考研英语语法】代词练习题
  4. 【英语:语法基础】B1.核心语法-名词与代词
  5. 【英语语法入门】 第12讲 代词:指示代词和不定代词
  6. 2021-2022 CSU C语言期末考试二
  7. 简单 洛谷 P1563 【模拟】玩具谜题普及场
  8. 安卓应用修改系统时间
  9. Forecasting: Principles and Practice读书笔记Chapter 3
  10. 关于二叉树重构的思索