- 信号处理器函数设置全局标志变量并退出,主程序对此标记进行周期检查,一旦置位随即采起相应动做
- 信号处理器函数执行某种类型的清理,以后终止进程或者使用非本地跳转将栈解开并将控制返回主程序定位置
- 可重入:
- 同一进程多线程能够同时安全的调用一个函数(信号处理函数可在任意时间点中断程序的执行,从而在一个进程中造成2条独立但不是并发的执行线程)
- 异步信号安全:
- 若是一个函数是可重入的或者信号处理函数没法将其中断
- 仅当信号处理函数中断了不安全函数的执行,且处理函数自身也调用了这个函数时,该函数才是不安全的
实现的两种策略linux
- 确保信号处理器函数代码自己是可重入的,且只调用异步信号安全的函数
- 当主程序执行不安全函数或者是去处理信号处理器函数可能更新的全局数据结构时,阻塞信号的传递
全局变量和sig_atomic_tredis
volatile
避免将全局变量优化的寄存器sig_atomic_t
保证读写原子性- 主程序和信号处理器函数共享的全局变量声明以下
volatile sig_atomic flag
信号处理器函数终止的方法shell
- 调用_exit(),不是exit()(会清除I/O缓冲不安全)
- 调用kill()来杀掉进程
- 信号处理器函数执行非本地跳转:
- 略
- 调用abort()终止进程并产生核心转储
- 略
系统调用的中断和重启编程
信号处理器函数中断了阻塞的系统调用时,系统调用会产生EINTR报错api
- 内含进程终止时内存镜像的一个文件(默认进程工做目录/core)
- 文件命名/proc/sys/kernel/core_pattern
SIGKILL
和SIGSTOP
的默认行为没法更改,调用signal()
和sigaction()
来改变时总会返回错误;同时也不能阻塞SIGCONT
若是一个进程处于中止状态,SIGCONT
总会使其回复;进程收到SIGCONT
会将等待状态的中止信号丢弃,反之收到中止信号,会将等待状态的SIGCONT
丢弃
内核常常须要领进程休眠,休眠有两种安全
- TASK_INTERRUPTIBLE:等待某一事件(终端输入等等),传递进来的信号会唤醒进程 ps STAT :S
- TASK_UNINTERRUPTIBLE:等待特定事件(磁盘I/O完成等的),在摆脱状态前,系统不会把信号传递给进程 ps STAT:D
- 通常这种状态转瞬即逝,可是若是由于硬件故障等问题 由于(
SIGKILL
,SIGSTOP
)不会终止挂起进程,只能经过重启- TASK_KILLABLE:相似于前者,可是收到杀死进程信号会唤起
- 同步信号(进程本身产生的信号,本身调用kill(), raise()等等)会当即传递
- 异步信号在进程发生内核态到用户态的下一次切换时
- 进程在前度超时后,再度得到调用
- 系统调用完成
若是经过
sigprocmask()
解除了多个等待信号的阻塞。这些信号会马上传给进程bash
- Linux下一般是根据信号编号升序传递
- 同时若是调度器函数引发了内核态和用户态的切换,会中断函数转而调用下一个信号处理器函数
![]()
- 信号范围扩大
- 队列化管理,若是将某一实时信号的多个实例发给一进程,信号会屡次传递
- 可为信号指定伴随数据
- 传递顺序获得保障,等待恢复后(信号编号小加时间早先传递)
#define _POSX_C_SOURCE 199309
#include<signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
// 权限和kill()一致,可是pid不能为负
union sigval{
int sival_int;
void *sival_ptr;
}
复制代码
#include<signal.h>
int sigsuspend(const sigset_t *mask);
//sigsuspend(&mask) 等同于
sigprocmask(SIG_SETMASK, &mask, &prevMask);
pause();
sigprocmask(SIG_SETMASK, &prevMask, NULL);
复制代码
调用,捕获,文件描述符获取信号略数据结构
限制比较多多线程
- 信号异步的本质,可重入性,竟态条件,全局变量的设置
- 标准信号没法排队,实时信号排队数量限制
- 携带信息有限
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
void sig_handler(int signum) {
printf("in handler\n");
sleep(1);
printf("handler return\n");
}
int main(int argc, char **argv) {
char buf[100];
int ret;
struct sigaction action, old_action;
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
/* 版本1:不设置SA_RESTART属性 * 版本2:设置SA_RESTART属性 */
//action.sa_flags |= SA_RESTART;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGINT, &action, NULL);
}
bzero(buf, 100);
ret = read(0, buf, 100);
if (ret == -1) {
perror("read");
}
printf("read %d bytes:\n", ret);
printf("%s\n", buf);
return 0;
}
// ctr+c 以后一个会ret=-1 一个会从新执行
复制代码
#include<sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
// 0 success, -1 on error
int getitimer(int which, struct itimerval *curr_value);
// curr_value值和old_value值一致
struct itimerval{
struct timeval it_interval;
struct timeval it_value;
};
struct timeval{
time_t tv_sec;
suseconds_t tv_usec;
}
复制代码
which取值并发
- ITIMER_REAL:真实时间倒计时的定时器,到期产生SIGALARM信号发送给进程
- ITIMER_VIRTUAL:用户模式下CPU时间倒计时计时器,到期产生SIGVTALRM
- ITIMER_PROF:进程时间(用户态和内核态CPU时间总和)的倒计时器,到期产生SIGPROF
new_value取值
- it_value 指定了延迟时间
- it_interval若是两个字段为0,则为一次性定时器,不然,在每次定时器到期后,会重置定时器指定间隔后再到期
- 若是调用setitimer时new_value 的 it_value的字段都为0,则屏蔽已有定时器
SUSv4废弃了
getitimer()
和setitimer()
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
// 一次性定时器,返回剩余时间,alarm(0)屏蔽全部定时器,根据操做系统实现决定是否是和setitimer共享屏蔽
复制代码
- 可经过定时器函数结合
sigsuspend
实现sleep
include<unistd.h>
unsigned int sleep(unsigned int seconds);
//正常结束返回0,若是被终止返回剩余秒数
复制代码
略
- 系统调用
fork()
:子进程获取父进程的栈,数据段,堆和执行文本段的拷贝,同一程序文本段- 库函数
exit(status)
(时系统调用_exit(status)
的外层),将进程所占用的全部资源归还内核;父进程经过wait()
获取该状态- 系统调用
wait(&status)
- 若是子进程未经过
exit()
终止,挂起父进程直至子进程终止- 子进程状态经过
status
参数返回- 父子进程通常只有一个会
exit()
退出,另外一个使用_exit()
退出- 系统调用
execve(pathname, argv, envp)
加载一个新程序到当前内存,丢弃现存的程序文本段,并从新建立栈,数据段以及堆![]()
fork()
完成
fork()
的调用后两个进程均会从fork()
处返回,父进程返回子进程的pid,子进程返回0,没法建立时返回-1(多是进程数超过了real_user_id的进程数上限或者系统级上限)
pid_t childPid;
switch (childPid = fork()){
case -1:
/*...*/
case 0:
/* child perform */
default:
/* parent perform */
}
复制代码
fork()
文件描述符的建立相似dup()
,指向相同的打开文件句柄,即文件偏移量等均是共享的,父子进程不会覆盖彼此的输出,但会乱序;shell中不加&
父进程会等待子进程结束
fork()
内存语义
- 原意是对程序段,数据段,堆段和栈段建立拷贝
- 会形成浪费,好比
fork()
以后的exec()
从新初始化....- 优化的措施
- 内核将每个进程的代码段标记成RO,fork()为子进程的构建代码段时,其所构建的一系列进程级页表均指向父进程相同那个的物理内存页帧
- 数据段,堆段,栈段中的各页,内核采用写时复制(copy-on-write)以前(redis BUG有遇到过):将这些段的页表指向父进程物理地址相同的物理内存页,并将这些页标记成只读,以后为要修改的页创建copy
vfork()
- 尽可能避免使用
- 适用于为子进程马上调用
exec()
而设计- 无需为子进程复制虚拟内存页活页表,共享父进程内存,直到成功调用exec()或_exit(),(文件描述符表每一个进程是独立的)
- 在子进程调用exec()和_exit()以前暂停父进程
- 能保证调用
vfork()
以后子进程先于父进程得到CPU调度
fork()
以后的竞态条件
/proc/sys/kernel/sched_child_runs_first
为0 则fork()
以后父进程先调度
- 经过信号终止进程,可能产生核心转储。
- 经过调用
_exit()
正常终止,main()
函数return n
等同于exit(n)
- 若是在推出的处理过程当中所执行的任何步骤须要访问
main()
本地变量,那么从main()
返回会致使未定义的行为(ex:setbuff()
调用本地变量)
#include<unistd.h>
void _exit(int status);
//调用永远成功
复制代码
exit()
#include<stdlib.h>
void exit(in status);
// 不是异步信号安全函数
复制代码
- 调用退出处理程序(经过
atexit()
和on_exit()
注册的函数),执行顺序与注册顺序相反- 刷新
stdio
流缓冲区- 使用
status
执行_exit()
- 关闭全部的打开文件描述符,目录流,信息目录描述符?以及转换描述符?
- 以后章节涉及的细节待补充。
#include<stdlib.h>
int atexit(void (*func) (void));
// 0 on success
复制代码
#define _BSD_SOURCE
#include<stdlib.h>
int on_exit(void (*func)(int, void *), void *args);
// 和 atexit是在同一列表注册,同样的执行和注册顺序相反
复制代码
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(){
printf("Hello\n");
write("STDOUT_FILENO", "WORLD\n",6);
if(fork() == -1){
exit(-1);
}
exit(EXIT_SUCCESS);
}
复制代码
$ gcc fork_io.c -o fork_io
$ ./fork_io
Hello
WORLD
$ ./fork_io > 1.txt | cat 1.txt
WORLD
Hello
Hello
//终端为行缓冲,重定向到文件为块缓冲
复制代码
避免方法
fork()
以前fflush()
刷新缓冲区,或调整stdio的缓冲选择- 确认的状况下子进程调用_exit()(应该不是很合适)