SocketIO通讯
IO模型
- 非阻塞IO处理
消耗cpu - 阻塞IO处理
消耗资源 - 多路IO复用
内核监听多个文件描述符的属性(读写缓冲区)变化
某个文件描述符的读缓冲区变化了,可以读了,将这个事件告知应用层 - 异步IO
- 信号驱动式IO模型
signal
IO复用
select
跨平台,windows使用较多
typedef struct
{
/* XPG4.2 requires this member name. Otherwise avoid the name
from the global namespace. */
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];//1024/(8*8)=16
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set;
简化
typedef struct{
long int fds_bits[16];
}fd_set;
//fd_set就是一个long int类型的数组。因为每一位可以代表一个文件描述符。所以fd_set最多表示1024个文件描述符
//long int 是8字节,8*8=64位,64*16=1024位,1个位表示一个文件描述符,共1024个文件描述符
宏
#define FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp) //加入句柄
//#define FD_SET(fd,fdsetp) fdsetp->__fds_bits[fd/64] |= (long int)1<<(d%64)
//描述符15 __fds_bits[15/64]=>__fds_bits[0]的第15位置1
//描述符127 __fds_bits[127/64]=>__fds_bits[1]的第63位置1==(1<<(127%64))
#define FD_CLR(fd, fdsetp) __FD_CLR (fd, fdsetp) //移除句柄
#define FD_ISSET(fd, fdsetp) __FD_ISSET (fd, fdsetp)
//检测fd在fdset集合中的状态是否变化,当检测到fd状态发生变化时返回真,否则,返回假(也可以认为集合中指定的文件描述符是否可以读写)
#define FD_ZERO(fdsetp) __FD_ZERO (fdsetp) //初始化(fd_set->fds_bits数组成员设置为0),可理解为初始化一个装有fd的文件夹
select (int __nfds, fd_set *__restrict __readfds,
fd_set *__restrict __writefds,
fd_set *__restrict __exceptfds,
struct timeval *__restrict __timeout)
//nfds:集合中所有文件描述符的范围,为所有文件描述符的最大值加1
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
fd_set set;
// long int l=(long int)1<<(127%64);
FD_ZERO(&set);
FD_SET(10,&set);
FD_SET(65,&set);
int i;
for(i = 0;i < sizeof(set);i++)
{
printf("set.__fds_bits[%d]:%ld\t",i,set.__fds_bits[i]);
}
printf("\n/\n");
FD_SET(1,&set);
for(i = 0;i < sizeof(set);i++)
{
printf("set.__fds_bits[%d]:%ld\t",i,set.__fds_bits[i]);
}
printf("\n/\n");
FD_ZERO(&set);
FD_SET(16,&set);
for(i = 0;i < sizeof(set);i++)
{
printf("set.__fds_bits[%d]:%ld\t",i,set.__fds_bits[i]);
}
return 0;
}
poll
nt poll (struct pollfd *fds, unsigned int nfds, int timeout);
参数解释:
(1)fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件
(2)nfds:表示fds结构体数组的长度
(3)timeout:表示poll函数的超时时间,单位是毫秒
函数功能:
监视并等待多个文件描述符的属性变化
函数返回值:
(1)返回值小于0,表示出错
(2)返回值等于0,表示poll函数等待超时
(3)返回值大于0,表示poll由于监听的文件描述符就绪返回,并且返回结果就是就绪的文件描述符的个数
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
`fd`:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。
`events`:表示要告诉操作系统需要监测fd的事件(输入、输出、错误),每一个事件有多个取值
`revents`:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回
events&revents
事件 | 描述 | 是否可作为输入(events) | 是否可作为输出(revents) |
---|---|---|---|
POLLN | 数据可读(包括普通数据&优先数据) | 是 | 是 |
POLLOUT | 数据可写(普通数据&优先数据) | 是 | 是 |
POLLRDNORM | 普通数据可读 | 是 | 是 |
POLLRDBAND | 优先级带数据可读(linux不支持) | 是 | 是 |
POLLPRI | 高优先级数据可读,比如TCP带外数据 | 是 | 是 |
POLLWRNORM | 普通数据可写 | 是 | 是 |
POLLWRBAND | 优先级带数据可写 | 是 | 是 |
POLLRDHUP | TCP连接被对端关闭,或者关闭了写操作,由GNU引入 | 是 | 是 |
POPPHUP | 挂起 | 否 | 是 |
POLLERR | 错误 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
读就绪:
1)socket内核中,接收缓冲区中的字节数,大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
2)socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
3)监听的socket上有新的连接请求;
4)socket上有未处理的错误;
写就绪:
1)socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大⼩), 大于等于低水位标记 SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
2)socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发 SIGPIPE信号;
3)socket使⽤非阻塞connect连接成功或失败之后;
4)socket上有未读取的错误;
异常就绪:
socket上收到带外数据
epoll
linux使用
没有1024文件描述符限制
不需要拷贝监听的描述符到内核
返回已变化的描述符,不需要遍历树
cat /proc/sys/fs/epoll/max_user_watches
800890
注册到epoll实例中的最大文件描述符的数量限制
函数
epoll_create
: 创建一个epoll实例,文件描述符红黑树
#include <sys/epoll.h>
int epoll_create(int size);
参数:
size : 监听的文件描述符的上限, 2.6版本之后写1即可,
返回: 返回树的句柄
epoll_ctl
: 将监听的文件描述符添加到epoll实例中(上树 下树 修改节点),实例代码为将标准输入文件描述符添加到epoll中
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd : 树的句柄,epoll实例
op : EPOLL_CTL_ADD 上树
EPOLL_CTL_DEL 下树
EPOLL_CTL_MOD 修改
fd : 上树,下树的文件描述符
event : 上树的节点
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */ 需要监听的事件
epoll_data_t data; /* User data variable */ 需要监听的文件描述符
};
事件宏 events
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT: 表示对应的文件描述符可以写;
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR: 表示对应的文件描述符发生错误;
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将 EPOLL设为边缘触发(Edge Triggered)模式(默认为水平触发),这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
epoll_wait
: 监听;等待epoll事件从epoll实例中发生, 并返回事件以及对应文件描述符
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能: 监听树上文件描述符的变化
epfd : 树的句柄
events : 接收变化的节点的数组的首地址
maxevents : 数组元素的个数
timeout : -1 永久监听 大于等于0 限时等待
返回值: 返回的是变化的文件描述符个数
事件模型(epoll_wait工作方式)
水平触发(level-triggered)LT(默认)
- 持续的高电平或持续的低电平
- socket接收缓冲区不为空 有数据可读 读事件一直触发
- socket发送缓冲区不满 可以继续写入数据 写事件一直触发
一直触发epoll_wait耗费资源
边沿触发(edge-triggered)ET(常用)
- 电平有高到低的一个变化或由低到高的变化
- socket的接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
- socket的发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件
读写缓冲区一次全部读取或写满
//设置cfd为非阻塞(循环读取缓冲区)
int fl=fcntl(sock,F_GETFL);
fcntl(sock,F_SETFL,fl|O_NONBLOCK);
//设置cfg读边沿触发,不设置lfd
ev.data.fd=cfd;
ev.events=EPOLLIN | EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
//判断lfd变化,并且是读事件变化
if(evs[i].data.fd == lfd && evs[i].events & EPOLLIN)
{
struct sockaddr_in cliaddr;
char ip[16]="";
socklen_t len = sizeof(cliaddr);
int cfd = Accept(lfd,(struct sockaddr *)&cliaddr,&len);//提取新的连接
printf("new client ip=%s port =%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16)
,ntohs(cliaddr.sin_port));
//设置cfd为非阻塞
int flags = fcntl(cfd,F_GETFL);//获取的cfd的标志位
flags |= O_NONBLOCK;
fcntl(cfd,F_SETFL,flags);
//将cfd上树
ev.data.fd =cfd;
ev.events =EPOLLIN | EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
}
else if (evs[i].events & EPOLLIN) //cfd变化 ,而且是读事件变化
{
//循环读取
while (1)
{
char buf[4] = "";
//如果读一个缓冲区,缓冲区没有数据,如果是带阻塞,就阻塞等待,如果
//是非阻塞,返回值等于-1,并且会将errno 值设置为EAGAIN
int n = read(evs[i].data.fd, buf, sizeof(buf));
if (n < 0) //出错,cfd下树
{
//如果缓冲区读干净了,这个时候应该跳出while(1)循环,继续监听
if (errno == EAGAIN)
{
break;
}
//普通错误
perror("");
close(evs[i].data.fd); //将cfd关闭
epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]);
break;
}
else if (n == 0) //客户端关闭 ,
{
printf("client close\n");
close(evs[i].data.fd); //将cfd关闭
epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); //下树
break;
}
else
{
// printf("%s\n",buf);
write(STDOUT_FILENO, buf, 4);
write(evs[i].data.fd, buf, n);
}
}
}
边沿触发仅触发一次,水平触发会一直触发
libevent 采用水平触发, nginx 采用边沿触发
信号驱动式IO模型
signal
程A给进程B发送信号,进程B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕后再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。
信号类型
Linux系统共定义了64种信号,分为两大类:可靠信号与不可靠信号,前32种信号为不可靠信号,后32种为可靠信号
- 不可靠信号: 也称为非实时信号,不支持排队,信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值取值区间为1~31;
- 可靠信号: 也称为实时信号,支持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64
信号表
在终端,可通过kill -l
查看所有的signal信号
取值 | 名称 | 解释 | 默认动作 |
---|---|---|---|
1 | SIGHUP | 挂起 | |
2 | SIGINT | 中断 | |
3 | SIGQUIT | 退出 | 不能被忽略、处理和阻塞 |
4 | SIGILL | 非法指令 | |
5 | SIGTRAP | 断点或陷阱指令 | |
6 | SIGABRT | abort发出的信号 | |
7 | SIGBUS | 非法内存访问 | |
8 | SIGFPE | 浮点异常 | |
9 | SIGKILL | kill信号 | 不能被忽略、处理和阻塞 |
10 | SIGUSR1 | 用户信号1 | |
11 | SIGSEGV | 无效内存访问 | |
12 | SIGUSR2 | 用户信号2 | |
13 | SIGPIPE | 管道破损,没有读端的管道写数据 | |
14 | SIGALRM | alarm发出的信号 | |
15 | SIGTERM | 终止信号 | |
16 | SIGSTKFLT | 栈溢出 | |
17 | SIGCHLD | 子进程退出 | 默认忽略 |
18 | SIGCONT | 进程继续 | |
19 | SIGSTOP | 进程停止 | 不能被忽略、处理和阻塞 |
20 | SIGTSTP | 进程停止 | |
21 | SIGTTIN | 进程停止,后台进程从终端读数据时 | |
22 | SIGTTOU | 进程停止,后台进程想终端写数据时 | |
23 | SIGURG | I/O有紧急数据到达当前进程 | 默认忽略 |
24 | SIGXCPU | 进程的CPU时间片到期 | |
25 | SIGXFSZ | 文件大小的超出上限 | |
26 | SIGVTALRM | 虚拟时钟超时 | |
27 | SIGPROF | profile时钟超时 | |
28 | SIGWINCH | 窗口大小改变 | 默认忽略 |
29 | SIGIO | I/O相关 | |
30 | SIGPWR | 关机 | 默认忽略 |
31 | SIGSYS | 系统调用异常 | |
对于signal信号,绝大部分的默认处理都是终止进程 或停止进程 ,或dump内核映像转储。 上述的31的信号为非实时信号,其他的信号32-64 都是实时信号 |
信号产生
硬件方式
- 用户输入:比如在终端上按下组合键ctrl+C,产生SIGINT信号;
- 硬件异常:CPU检测到内存非法访问等异常,通知内核生成相应信号,并发送给发生事件的进程;
软件方式
通过系统调用,发送signal信号:kill(),raise(),sigqueue(),alarm(),setitimer(),abort()
信号注册和注销
注册
在进程task_struct结构体中有一个未决信号的成员变量 struct sigpending pending。每个信号在进程中注册都会把信号值加入到进程的未决信号集。
- 非实时信号发送给进程时,如果该信息已经在进程中注册过,不会再次注册,故信号会丢失
- 实时信号发送给进程时,不管该信号是否在进程中注册过,都会再次注册。故信号不会丢失
注销
- 非实时信号:不可重复注册,最多只有一个sigqueue结构;当该结构被释放后,把该信号从进程未决信号集中删除,则信号注销完毕;
- 实时信号:可重复注册,可能存在多个sigqueue结构;当该信号的所有sigqueue处理完毕后,把该信号从进程未决信号集中删除,则信号注销完毕
信号处理
内核处理进程收到的signal是在当前进程的上下文,故进程必须是Running状态。当进程唤醒或者调度后获取CPU,则会从内核态转到用户态时检测是否有signal等待处理,处理完,进程会把相应的未决信号从链表中去掉
处理时机
signal信号处理时机: 内核态 -> signal信号处理 -> 用户态:
- 在内核态,signal信号不起作用;
- 在用户态,signal所有未被屏蔽的信号都处理完毕;
- 当屏蔽信号,取消屏蔽时,会在下一次内核转用户态的过程中执行;
处理方式
进程对信号的处理方式: 有3种
- 默认 接收到信号后按默认的行为处理该信号。 这是多数应用采取的处理方式。
SIG_DFL默认动作处理程序
signal(signum,SIG_DFL)
- 忽略 接收到信号后不做任何反应
SIG_IGN忽略信号处理程序
signal(signum,SIG_IGN)
例: signal(SIGINT,SIG_IGN)
signal(SIGPIPE,SIG_IGN)
SIG_DFL,SIG_IGN 分别表示无返回值的函数指针,指针值分别是0和1,这两个指针值逻辑上讲是实际程序中不可能出现的函数地址值。
- 自定义 用自定义的信号处理函数来执行特定的动作
信号安装
进程处理某个信号前,需要先在进程中安装此信号。安装过程主要是建立信号值和进程对相应信息值的动作。
信号安装函数
signal()
:不支持信号传递信息,主要用于非实时信号安装;sigaction()
:支持信号传递信息,可用于所有信号安装;
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
- signum:要操作的signal信号。
- act:设置对signal信号的新处理方式。
- oldact:原来对信号的处理方式。
- 返回值:0 表示成功,-1 表示有错误发生
sigaction
结构体
struct sigaction {
union
{
/* 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
sigset_t sa_mask;//信号集
int sa_flags;
void (*sa_restorer)(void);//已过时,POSIX不支持它,不应再被使用
- sa_handler和sa_sigaction是信号处理函数,二者选其一(选择sa_handler和signal一样),选择sa_sigaction,设置sa_flags=SA_SIGINFO
- void (*sa_sigaction) (int signo, siginfo_t *info, void *p);
int signo:内核返回的信号值
siginfo_t *info:内核返回当前信号的信息
void *p:sigqueue发送信息会携带sigval联合体,p就指向这个联合体
- void (*sa_sigaction) (int signo, siginfo_t *info, void *p);
- sa_mask:定义阻塞信号集(屏弊的信号集合),指定信号处理程序执行过程中需要阻塞的信号
- sa_flags:标示位
- SA_RESTART:使被信号打断的syscall重新发起。
- SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
- SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵 尸进程。
- SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
- SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
- SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数。
typedef struct
{
int si_signo; /* Signal number. */
#if __SI_ERRNO_THEN_CODE
int si_errno; /* If non-zero, an errno value associated with
this signal, as defined in <errno.h>. */
int si_code; /* Signal code. */
#else
int si_code;
int si_errno;
#endif
#if __WORDSIZE == 64
int __pad0; /* Explicit padding. */
#endif
union
{
int _pad[__SI_PAD_SIZE];
/* kill(). */
struct
{
__pid_t si_pid; /* Sending process ID. */
__uid_t si_uid; /* Real user ID of sending process. */
} _kill;
/* POSIX.1b timers. */
struct
{
int si_tid; /* Timer ID. */
int si_overrun; /* Overrun count. */
__sigval_t si_sigval; /* Signal value. */
} _timer;
/* POSIX.1b signals. */
struct
{
__pid_t si_pid; /* Sending process ID. */
__uid_t si_uid; /* Real user ID of sending process. */
__sigval_t si_sigval; /* Signal value. */
} _rt;
/* SIGCHLD. */
struct
{
__pid_t si_pid; /* Which child. */
__uid_t si_uid; /* Real user ID of sending process. */
int si_status; /* Exit value or signal. */
__SI_CLOCK_T si_utime;
__SI_CLOCK_T si_stime;
} _sigchld;
/* SIGILL, SIGFPE, SIGSEGV, SIGBUS. */
struct
{
void *si_addr; /* Faulting insn/memory ref. */
__SI_SIGFAULT_ADDL
short int si_addr_lsb; /* Valid LSB of the reported address. */
union
{
/* used when si_code=SEGV_BNDERR */
struct
{
void *_lower;
void *_upper;
} _addr_bnd;
/* used when si_code=SEGV_PKUERR */
__uint32_t _pkey;
} _bounds;
} _sigfault;
/* SIGPOLL. */
struct
{
__SI_BAND_TYPE si_band; /* Band event for SIGPOLL. */
int si_fd;
} _sigpoll;
/* SIGSYS. */
#if __SI_HAVE_SIGSYS
struct
{
void *_call_addr; /* Calling user insn. */
int _syscall; /* Triggering system call number. */
unsigned int _arch; /* AUDIT_ARCH_* of syscall. */
} _sigsys;
#endif
} _sifields;
} siginfo_t __SI_ALIGNMENT;
/* X/Open requires some more fields with fixed names. */
#define si_pid _sifields._kill.si_pid
#define si_uid _sifields._kill.si_uid
#define si_timerid _sifields._timer.si_tid
#define si_overrun _sifields._timer.si_overrun
#define si_status _sifields._sigchld.si_status
#define si_utime _sifields._sigchld.si_utime
#define si_stime _sifields._sigchld.si_stime
#define si_value _sifields._rt.si_sigval
#define si_int _sifields._rt.si_sigval.sival_int
#define si_ptr _sifields._rt.si_sigval.sival_ptr
#define si_addr _sifields._sigfault.si_addr
#define si_addr_lsb _sifields._sigfault.si_addr_lsb
#define si_lower _sifields._sigfault._bounds._addr_bnd._lower
#define si_upper _sifields._sigfault._bounds._addr_bnd._upper
#define si_pkey _sifields._sigfault._bounds._pkey
#define si_band _sifields._sigpoll.si_band
#define si_fd _sifields._sigpoll.si_fd
#if __SI_HAVE_SIGSYS
# define si_call_addr _sifields._sigsys._call_addr
# define si_syscall _sifields._sigsys._syscall
# define si_arch _sifields._sigsys._arch
#endif
请留意两个用于传输数据的变量:
sigval_t si_value; /* Signal value /
int si_int; / POSIX.1b signal */
实际上:
siginfo_t 结构体中(sigval_t) si_value就是sigqueue函数中传入的第三个参数sigval
siginfo_t 结构体中(int) si_int就是从sigqueue函数中传入的第三个参数sigval.sival_int中获得
siginfo_t 结构体中si_ptr就是从sigqueue函数中传入的第三个参数sigval.sival_ptr中获得
所以可以通过sigqueue种对sigval值的传入来实现信号带值,在信号处理函数中void sa_sigaction(int num,siginfo_t *info,void *d)读取第二个参数中对应变量的值来获取信号所带参数,通过这种方法实现进程间通讯
sigaction-sa_handler
使用
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void myHandler(int sig);
int main(int argc,char *argv[])
{
struct sigaction act,oact;
act.sa_handler=myHandler;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGINT,&act,&oact);
sigaddset(&act.sa_mask,SIGINT);
while (1)
{
printf("hello\n");
pause();//信号会打断阻塞
}
return 0;
}
void myHandler(int sig)
{
printf("i got signal: %d\n",sig);
}
sigaction-sa_sigaction
使用
#include <stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void sa_sigaction_func(int signo,siginfo_t *info,void *p)
{
printf("signo=%d\n",signo);
printf("sender pid=%d\n",info->si_pid);
printf("si_errno=%d\n",info->si_errno);
printf("si_code=%d\n",info->si_code);
printf("si_uid=%d\n",info->si_pid);
printf("si_status=%d\n",info->si_status);
printf("si_value=%d\n",info->_sifields._rt.si_sigval.sival_int);
printf("si_fd=%d\n",info->si_fd);
}
int main(int argc,char *argv[])
{
struct sigaction act,oact;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=sa_sigaction_func;
sigaddset(&act.sa_mask,10);
sigaction(SIGUSR1,&act,&oact);
while (1)
{
printf("pid is %d Hello\n",getpid());
pause();
}
}
信号集操作函数
- sigemptyset(sigset_t *set):信号集全部清0;
- sigfillset(sigset_t *set): 信号集全部置1,则信号集包含linux支持的64种信号;
- sigaddset(sigset_t *set, int signum):向信号集中加入signum信号;
- sigdelset(sigset_t *set, int signum):向信号集中删除signum信号;
- sigismember(const sigset_t *set, int signum):判定信号signum是否存在信号集中
信号阻塞屏蔽函数
- sigprocmask(int how, const sigset_t *set, sigset_t *oldset)); 不同how参数,实现不同功能
- SIG_BLOCK:将set指向信号集中的信号,添加到进程阻塞信号集;
- SIG_UNBLOCK:将set指向信号集中的信号,从进程阻塞信号集删除;
- SIG_SETMASK:将set指向信号集中的信号,设置成进程屏蔽信号集;
- sigpending(sigset_t *set)):(
未决信号
)获取已发送到进程,却被屏蔽的所有信号; - sigsuspend(const sigset_t *mask)):用mask代替进程的原有掩码,并暂停进程执行,直到收到信号再恢复原有掩码并继续执行进程。
信号发送
- kill():用于向进程或进程组发送信号;
sigqueue
():只能向一个进程发送信号,不能像进程组发送信号;主要针对实时信号提出,与sigaction()组合使用,当然也支持非实时信号的发送;
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
参数:
pid:是指定接收信号的进程id,
sig:确定即将发送的信号,
sigva
l value:是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
返回值成功返回0,失败返回-1
sigval
union sigval {
int sival_int;
void *sival_ptr;
};
siginfo_t结构中si_value要么持有一个4字节的整数值,要么持有一个指针,这就构成了与信号相关的数据
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.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=222;
//发送SIGUSR1
sigqueue(pid,SIGUSR1,v);
return 0;
}
- alarm():用于调用进程指定时间后发出SIGALARM信号;
- ualarm(1,1);
- setitimer():设置定时器,计时达到后给进程发送SIGALRM信号,功能比alarm更强大;
- abort():向进程发送SIGABORT信号,默认进程会异常退出。
- raise():用于向进程自身发送信号;
- pause(void):将调用进程挂起直至捕捉到信号为止。这个函数通常用于判断信号是否已到
信号集
数据类型sigset_t来存储,sigset_t称为信号集
sigset_t
结构体(信号集)
#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int))) //16
typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];//数组长度16,一个unsigned long int是8位,共128位
} __sigset_t;
信号集是以位图呈现
- 信号递达
实际执行信号的处理动作称为信号递达(Delivery) - 未决信号
信号从产生到递达之间的状态,称为信号未决(Pending)
把信号设置成为屏蔽,在向这个进程发送屏蔽的信号就会成为未决信号
设置为屏蔽信号到解除屏蔽号,这段时间内不管接收几次最后解除屏蔽信号只会处理一次未决信号
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
void handler(int sig){
printf("get a sig,num is:%d\n",sig);
}
void print_sig(sigset_t *p)
{
int i=1;
for(;i<32;++i){
if(sigismember(p,i)){
printf("1");
}else{
printf("0");
}
}
printf("\n");
}
int main(){
signal(2,handler);
sigset_t s,p,o;
sigemptyset(&s);
sigemptyset(&o);
sigaddset(&s,SIGINT);
sigprocmask(SIG_SETMASK,&s,&o);//信号集s中的SIGINT设置为屏蔽
int count=0;
while (1)
{
sigemptyset(&p);
sigpending(&p);//当前进程已收到的未决信号(屏蔽的信号),解决屏蔽时会执行一次P中的未决信号
print_sig(&p);
sleep(1);
if (count++==10)
{
sigprocmask(SIG_SETMASK,&o,NULL);//将之前的屏蔽信号集设置回去,并执行p未决集中的信号
printf("recover block\n");
sleep(3);
count=0;
}
}
}
- 信号阻塞
进程可以选择阻塞(Block)某个信号,SIGKILL 和 SIGSTOP 不能被阻塞。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志
未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略
定时器
两类定时器
- 实时:ITIMER_REAL,计时到达后发送SIGALRM信号
- 虚拟:ITIMER_VIRTUAL,计时到达后发送SIGVTALRM信号
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
void sigroutine(int signo){
switch (signo)
{
case SIGALRM:
printf("Catch a signal -- SIGALRM\n");
break;
case SIGVTALRM:
printf("Catch a signal -- SIGVTALRM\n");
break;
}
}
int main(){
struct itimerval value,ovalue,value2;
printf("process id is %d\n",getpid());
//自定义处理信号
signal(SIGALRM,sigroutine);
signal(SIGVTALRM,sigroutine);
value.it_interval.tv_sec=1;//设置秒,第一次计时到达秒数
value.it_interval.tv_usec=0;//设置徽秒
value.it_value.tv_sec=1; //间隔触发秒数
value.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&value,&ovalue);//第三个参数返回已存在的定时器值
value2.it_value.tv_sec=0;
value2.it_value.tv_usec=500000;
value2.it_interval.tv_sec=0;
value2.it_interval.tv_usec=500000;
setitimer(ITIMER_VIRTUAL,&value2,&ovalue);
for(;;);
}
**信号会打断阻塞函数**
可重入函数
阻塞函数被信号打断,阻塞函数返回值为-1,这时需要判断全局错误码,如果errno==EINTR,则重入被打断的阻塞函数
...
ACCEPT:
confd=accept(srvfd,(struct sockaddr*)(&tCliaddr),&tCliaddlen);
if(confd==-1){
if(errno==EINTR){
goto ACCEPT;
}else{
fprintf(stderr,"accept error");
return -1;
}
}
...
应用场景
超时响应