1.信号:是一个软中断--通知进程事件的发生
信号的生命周期:产生->注册->注销->处理
产生:(硬件/软件)
处理:(默认/忽略/自定义)
2.信号的产生
62种信号---使用kill -l命令查看
1-31 (非可靠信号/非实时信号)
34 -64 (可靠信号/实时信号)
硬件产生:ctrl+c ctrl+l ctlr+z
软件产生:kill命令 kill() raise() abort() alarm()
信号的到来会打断当前进程的阻塞操作
core dumped:核心转储---异常退出时保存程序运行信息
默认是关闭状态
ulimit -a 查看核心转储文件大小
ulimit -c 设置核心转储文件大小 ulimit -c 1024
core文件命令方式:core.pid
core文件的使用:
gdb ./loop -> core-file core.pid
先加载运行程序->加载程序运行信息->开始调试:bt查看调用栈信息
3.信息号注册
pcb->struct sigpending->_sigset_t(信号集合)
操作系统给一个进程发送信号,实际就是向这个进程pcb的信号pending集合中添加信号(修改位图)
因为位图只能标记信号是否存在,不能标记到来信号的个数
pcb有一个sigqueue链表,信号到来就会组织一个节点添加到这个链表中
可靠信号到来:修改位图(不管位图是否为1),每个信号都组织节点添加到链表中
非可靠信号到来:修改位图,若位图已经为1,则什么都不做;否则添加一个节点,修改位图
4.信号的注销
可靠信号:因为节点有可能有多个,删除节点后判断是否还有相同节点,判断是否修改位图
非可靠信号:因为节点只有一个,因此删除节点后,位图直接修改为0
5.信号的处理
默认:因为每个信号都对应一个事件,这些事件在操作系统中都有既定义完毕的处理方式
忽略:什么都不干
自定义:用户自己定义处理方式(函数)---修改内核中信号所对应的处理方式
信号的捕捉流程:自定义信号
信号的捕捉处理是在从程序运行内核态到用户态切换之前完成
1. 信号的处理方式选择则一个合适的时机去处理---从内核态运行切换到用户态运行之前看有没有信号需要被处理;
2. 如果有信号待处理,并且是自定义处理方式,调用do_signal处理信号,返回用户态执行我们自定的信号处理函数;
3. 用户自定义信号的处理函数执行完毕后,调用sig_return返回内核态;
4.再次查看是否有信号待处理,如果没有了,则返回用户态回到程序主控流程,执行程序。
一个进程如何从用户态切换到内核态(系统调用接口、程序异常、中断)
/*************************************************************************
> File Name: loop.c
> Author: ssh
> Mail: sunshihao163@163.com
> Created Time: Mon 15 Apr 2019 04:56:57 PM CST
************************************************************************/
#include<stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void sigcb(int signo){
printf("recv signo:%d\n", signo);
}
int main(){
//sighandler_t signal(int signum, sighandler_t handler);
// 修改信号在内核中的处理方式
// signum: 信号编号
// handler: 处理方式
// SIG_DFL 默认处理方式
// SIG_IGN 忽略处理方式
// typedef void(*sighandler_t)(int);
//signal(SIGQUIT, SIG_IGN);
signal(SIGQUIT, sigcb);
//int kill(pid_t pid, int sig);
// pid:指定信号要发送给那个进程的进程id
// sig:信号编号/usr/include/bits/signum.h
//kill(getpid(), SIGSEGV);
//int raise(int sig);
// 给调用进程发送SIGABRT信号
raise(SIGQUIT);
//void abort(void);
// 给调用进程发送SIGABRT信号
//abort();
//unsigned int alarm(unsigned int seconds);
// 定时器:在seconds秒之后向调用进程发送SIGALRM信号
// 返回值:上一个定时器剩余的时间或者0
//alarm(3);
while(1){
printf("hello bit!\n");
sleep(10);
}
return 0;
}
6.信号的阻塞:暂时不处理这个信号---阻止信号递达
信号的递达:动作--信号处理
信号的未决:状态--信号从产生到递达之间的状态
未决信号:还没有被处理的信号
sigset_t 信号集合(数组-位图)
信号进行阻塞:实际上就是在blocked位图中标记那些信号到来之后暂时不被处理
有两个信号是不会被阻塞,也不会被自定义:SIGKILL SIGSTOP
如何阻塞信号/解除阻塞
sigemptyset 清空信号集合 sigfillset 向集合中填充所有信号
sigaddset 向集合中添加指定信号 sigprocmask 从集合中删除指定信号
如何获取未决信号
sigpending 判断指定信号是否在集合中 mask & 1 << num
sigismemer 获取进程当前未决信号
先阻塞所有信号->getchar()->解除阻塞-----信号的阻塞;可靠/非可靠信号处理
mysleep实现:信号会打断当前进程的阻塞操作
alarm+pause----pause使进程阻塞,alarm保证n秒中之后发送信号打断阻塞
sigsuspend(mask)--临时使用mask替换阻塞集合,阻塞mask中的信号---陷入休眠---唤醒后还原阻塞信号
先阻塞SIGALRM防止定时器与休眠之间SIGALRM被处理,使用sigsuspend对SIGALRM解除阻塞,并且陷入休眠(原子操作)
/*************************************************************************
> File Name: mask.c
> Author: ssh
> Mail: sunshihao163@163.com
> Created Time: Mon 15 Apr 2019 05:09:50 PM CST
************************************************************************/
/* 信号的阻塞
* int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
* how:
* SIG_BLOCK 阻塞set集合中的信号,将原有阻塞放到old中
* SIG_UNBLOCK 对set集合中的信号解除阻塞
* SIG_SETMASK 将set集合中的信号添加到阻塞集合中
* */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void sigcb(int signo){
printf("recv signo:%d\n", signo);
}
int main(){
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = sigcb;
act.sa_flags = 0;
//int sigaction(int signum, struct sigaction *act, strcut sigactiong *oldset);
// signum: 信号编号
// act: 自定义处理动作
// oldset: 保存信号原有处理动作
sigaction(SIGINT, &act, NULL);
sigaction(SIGRTMIN+5, &act, NULL);
/* 定义一个集合,
* 向集合中添加要阻塞的信号
* 阻塞这个集合的所有信号
* getchar()
* 对集合中的信号解除阻塞
* */
sigset_t newset, oldset;
//int sigemptyset(sigset_t *set); 清空信号集合
sigemptyset(&newset);
//int sigfillset(sigset_t *set); 将所有信号添加到集合中
//int sigaddset(sigset_t *set, int signum); 将指定信号添加到集合中
sigfillset(&newset);
//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask(SIG_BLOCK, &newset, &oldset);
printf("all signal block, enter for unblocked\n");
getchar();
sigset_t set;
//int sigpending(sigset_t *set);
//获取未决信号
sigpending(&set);
int i;
for(i = 1; i <= 62; i++){
//int sigismember(const sigset_t *set, int signum);
//判断指定信号是否在集合中
if(sigismember(&set, i)){
printf("1 ");
}else{
printf("0 ");
}
}
printf("\n");
sigprocmask(SIG_UNBLOCK, &newset, NULL);
//sigprocmask(SIG_UNBLOCK, &oldset, NULL);
return 0;
}
7.竞态条件:多个执行时间之间出现的资源竞争问题
可重入/不可重入:能否在多个运行时序重复调用,而不造成数据二义/程序异常
可不可重入的关键点:是否对非原子操作的全局数据进行了操作
原子性操作:操作不可别打断
当用户进行函数接口设计及调用的时候,就要考虑接口是否可重入
不可重入情况:malloc和free---不可重入---内部有不受保护的全局链表操作;调用了标准I/O库函数,标准I/O库的实现都以不可重入的方式使用全局数据结构
关键字:volatile---保持内存可见性--防止编译器对代码进行过度优化
若要进行代码优化,就需要用户对进程有更多的可控---需要明白那些变量需要修饰
SIGCHLD信号:17号信号
一个进程退出后,操作系统通知父进程子进程已经退出(操作系统通过SIGCHLD信号通知父进程)
问题:SIGCHLD信号的默认处理方式就是忽略处理;
因为父进程不知道SIGCHLD信号什么时候到来(不知道子进程什么时候退出),因此创建子进程之后就要进行等待;但是这样造成父进程的浪费(什么都没做,就是死等子进程);
如果父进程直接对SIGCHLD信号进行了自定义处理方式,并且自定义的处理方式中包含了进程等待;这时候就不需要空等待了;
问题:等待进程退出以while循环等待
因为SIGCHLD信号是一个非可靠信号,只注册一次,意味着sigcb只会被回调一次,然而waitpid调用一次,只能处理一个进程退出;因此就尽可能的在回调中将退出的进程全部处理掉while(waitpid(-1, NULL, WNOHANG) > 0);只要返回值大于0就表示有子进程退出,那就一直处理,直到没有子进程退出(返回0)或者出错(返回-1),退出while循环