(1)信号:由用户、系统或进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常;
可由下述条件产生:
一个进程给其他进程发送信号
#include <sys/types.h>
#include <signal.h>
// 成功返回 0,失败返回 -1 并设置 errno
int kill( pid_t pid, int sig );
目标进程由 pid 指定
pid取值 | 含义 |
---|---|
pid > 0 | 信号发送给 PID 为 pid 的进程 |
pid = 0 | 信号发送给本进程组内的其他进程 |
pid = -1 | 发给除 init 进程外的所有进程,发送者需要拥有对目标进程发送信号的权限 |
pid < -1 | 发给 PGID 为 -pid 的进程组的所有成员 |
信号值 sig 为 0,不发送任何信号(检测目标进程或进程组是否存在,这种检测方式不可靠);
errno 为 EINVAL 表示无效的信号;EPERM 该进程没有权限给目标进程发送信号;ESRCH 目标进程或进程组不存在;
#include <signal.h>
typedef void (*__sighandler_t) ( int );
参数用来指示信号的类型,必须保证是可重入的;
#include <bits/signum.h>
#define SIG_DFL ((__sighandler_t) 0)
#define SIG_IGN ((__sighandler_t) 1)
SIG_IGN 表示忽略目标信号,SIG_DFL 表示使用信号默认处理方式,例如结束进程(Term)、忽略信号(Ign)、结束进程并生成核心转储文件(Corn)、暂停进程(Stop)、继续进程(Cont);
信号 | 默认行为 | 含义 |
---|---|---|
SIGHUP | Term | 控制终端挂起 |
SIGPIPE | Term | 往读端被关闭的管道或 socket 连接中写数据 |
SIGURG | Ign | socket 连接上接收到紧急数据 |
SIGALRM | Term | 由 alarm 或 setitimer 设置的实时闹钟超时引起 |
SIGCHLD | Ign | 子进程状态发送变化(退出或暂停) |
处于阻塞状态的系统调用接收到信号,并且为该信号设置了信号处理函数,默认情况下系统调用将被中断,其返回的 errno 为 EINTR;
可以使用 sigaction
为信号设置 SA_RESTART 标志以重新启动被该信号中断的系统调用;
Linux 独有:对默认行为是 Stop 的信号,没有设置信号处理函数,也可以中断某些系统调用(如 connect
、epoll_wait
)
为信号设置处理函数;
#include <signal.h>
_sighandler_t signal ( int sig, _sighandler_t _handler );
成功时返回一个函数指针,指向前一次该信号处理函数的函数指针(可能是默认处理函数指针 SIG_DEF 也可能是上一次调用 signal
函数传入的函数指针)
出错时返回 SIG_ERR,设置 errno;
struct sigaction
{
union
{
_sighandler_t sa_handler;
void (*sa_sigaction) ( int, siginfo_t*, void* );
} _sigaction_handler;
_sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
};
sa_handler 指定信号处理函数;sa_mask 设置进程的信号掩码;
sa_flags 设置程序收到信号时的行为;
选项 | 含义 |
---|---|
SA_RESTART | 重新调用被该信号终止的系统调用 |
更健壮的设置信号处理函数
// 成功时返回 0,失败返回 -1 设置 errno
int sigaction ( int sig, const struct sigaction* act, struct sigaction* oact );
act 指定新的信号处理方式,oact 输出先前的处理方式;
sigset_t 是一个长整型数组,每一位代表一个信号,共 1024 位;
(1)select
异常事件检测带外数据
(2)SIGURG 信号;
alarm
系统调用一次只会引起一次 SIGALRM 信号;
分别用来设置 socket 接收数据 / 发送数据超时时间,数据类型为 timeval(和 select
超时参数相同);
定时器通常包含至少两个成员:超时时间和任务回调函数;
按照超时时间做升序排序链表;
管理所有长时间处于非活动状态的连接
通过alarm
每隔一段时间,发送 SIGALRM 信号;
有新的连接到来时,会在 connfd 上设置定时器; 每当 connfd 上有数据可读时,会重置其定时器;
超时处理函数中会检测在升序链表中是否有定时器超时,若有,则删除其 fd 上的注册事件并关闭 fd;
IO 复用系统调用都可以设置超时参数,但可能会被就绪事件触发提前返回,因此需要不断更新定时参数以反馈剩余时间;
在系统调用执行前后记录当前时间,从而获得执行该系统调用的时间;
循环队列 + 哈希思想(类似手表),将循环队列划分为 N 个槽,槽之间的时间间隔为 SI;
插入定时器时,插入到每个 Slot 头部;
每过 SI 时间,调用 tick()
,判断当前 Slot 是否有到期的定时器,并向前走一个 Slot;
用最小堆实现
将堆顶定时器的超时值作为定时间隔,一旦 tick
被调用,堆顶定时器必然到期,再从剩余的定时器中找到超时时间最小的一个,并将这段时间间隔作为下一次的定时间隔;
(1)句柄与事件绑定,内核通过句柄向应用程序通知事件就绪;
在 Linux 下,IO事件对应的句柄就是文件描述符,信号事件对应的句柄就是信号值;定时器事件对应固定值;
(2)事件多路分发器;统一各系统的 IO 复用系统调用;
(3)事件处理器;实现业务逻辑;与句柄绑定;
(4)Reactor;
event 是一个事件处理器;
(1)event_base :相当于一个 Reactor 实例;
base->evsel 表示选择的 IO 复用机制,即 select
、epoll
等;
(2)event_new :创建事件处理器;参数为关联的 base、事件对应句柄、事件类型、具体处理函数;称为 initialized;
(3)event_add :内部调用 event_queue_insert,将事件处理器加入到各种事件队列中,根据 ev->ev_flags 进行区分;称为 pending;
(4)event_base_dispatch / event_base_loop:执行事件循环,遍历 event_base 上注册的事件直到有事件就绪,将就绪事件加入 active 事件队列中(按优先级划分的相应队列),调用相应的事件处理器;