进程信号

进程信号介绍:

操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一个),它也是用户进程之间通信和同步的一种原始机制。一个键盘中断或者一个错误条件(比如进程试图访问它的虚拟内存中不存在的位置等)都有可能产生一个信号。Shell也使用信号向它的子进程发送作业控制信号

简易来说,信号即是信号与操作系统的一种的沟通方式

 

信号的概念

信号是进程之间事件异步通知的一种方式,属于软中断。

 

信号

 

 kill -l命令可以查看信号

可以看到信号都有属于自己的编号和宏定义

  • 1~31的信号为普通信号
  • 34~64的信号为实时信号

注意:没有 32、33信号

 

信号产生

  • 1.通过终端按键产生信号
  • 2.调用系统函数向进程发信号
  • 3.由软件条件产生信号
  • 4.硬件异常产生信号

 

信号的处理不是立即执行的

假设你现在在做一件事情,这时候你的朋友叫你过去帮他做一件事情,这时候你去不了,因为你还在做自己手头上的事情,优先级更高,所以会选择先做好这件事情再去帮忙。

异步:即信号的发送不是同时的,有时候需要发送,而不是统一同时发送的。

因为信号产生是异步的,当信号产生的时候,对应的进程,可能正在做更重要的事情,进程暂时不处理这个信号。

 

信号处理有三种方式:

  • 1. 忽略此信号。
  • 2. 执行该信号的默认处理动作。
  • 3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。 自定义处理,由用户提供

 

忽略信号是处理信号的方式,处理的方式是忽略

而阻塞信号,是不处理信号

忽略和阻塞要分清

 

进程信号的术语

  • 递达:进程执行信号的处理动作
  • 信号未决:信号从产生到递达之间的状态
  • 阻塞:进程可以对信号阻塞,被阻塞的信号处于未决状态,直到进程解除信号的阻塞状态,才可以递达

 

signal函数

捕捉指定函数,并执行自定义函数

void handler(int signo)
 {
 cout<<"我是一个进程,刚刚获取了一个信号:"<<signo<<"cnt: "<<endl;
 }
int main()
{
 signal(2,handler);
 //捕捉2号信号,并执行handler函数
return 0;
}

注意:9号信号不允许被自定义捕捉

 

kill函数

给指定进程发送指定信号

kill(2,指定进程pid);

失败时返回-1

 

raise函数(信号)

给自己(该进程)发送指定信号

raise(2)

 

abort函数()

abort

给该进程发送6号信号,并终止进程

6号信号可以被捕捉,但捕捉后,进程依旧结束

 

sigset_t

是用来表示信号集类型的

sigset_t bsig,obsig

创建两个信号集

阻塞信号集也叫做当 前进程的信号屏蔽字(Signal Mask)

 

sigemptyset函数(&信号集)

初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有 效信号。

 

sigfillset函数 (&信号集)

初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系 统支持的所有信号。

 

sigaddset函数(&信号集,信号)

将指定信号添加到指定信号集中

 

sigpending函数(&信号集)

获取正在送往进程中被阻塞的信号合集 并放入指定信号集中

 

sigismember(&信号集,信号)

sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

 

注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信

 

sigprocmask函数(操作,set,oset)

把set的信号集修改obset 

检查或修改指定信号相关联的处理动作 可同时两种操作

 

有三种操作

  • SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号
  • SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号
  • SIG_SETMASK 设置当前信号屏蔽字为set所指向的值

 

进程崩溃的本质是该进程收到了异常信号

崩溃了不一定导致进程结束,可以捕获

 

小结:

  • 所有的信号产生,最终都要由OS来执行
  • 信号处理不一定是立即处理,而是在合适的时候
  • 信号不是被立即处理,是会被暂时记录下来,记录在进程PCB内
  • 一个进程在没有收到信号时,它知道遇到什么信号,该做什么反应
  • OS向进程发信号,实际上向进程控制块中,写入信号数据。

 

信号在内核表示的示意图

 

 

先看nending是否接收到信号,再看block是否要拦截,最后看handler的处理方式

 

刚才我们提到,进程信号,不是立即处理的,而是会被暂时记录下来,存储在进程PCB中,处理是在合适的时候

那这个合适的时候是什么?:当前进程从内核态切换回用户态时,进行信号的检测与处理

用户态切换到内核态 原因

  • 1系统调用
  • 2进程切换

 

 

 

 内核级页表 所有进程共享,只有一份

用户级页表 每一个进程都要一份 并且相互不同

 

 

无论进程怎么切换,都可以找到内核的代码和数据,前提要有权限,当前进程如何具备权限,访问这个内核页表,乃至访问内核数据。

要进行身份切换

进程是用户态,只能访问用户级页表

 进程是内核态,能访问用户级页表和内核级页表

 

如何区分进程是用户太和进程态

CPU内部都有对应的状态寄存器CR3,有比特位表示当前进程的状态。

0:内核态 3:用户态

 

内核态具有更高权限,可以访问所以代码

用户态只能访问自己的代码

 

我们的程序,会无数次直接或间接的访问系统级软硬件资源(管理者是OS)本质上,你并没有自己去操作这些软硬件资源,而是必须通过OS->无数次陷入内核(1.切换身份 2.切换页表)-》调用内核的代码-》完成访问的动作-》结果放回给用户(1.切换身份 2.切换页表)->得到结果

 

 这里我们会发现,当有自定义捕捉时,为什么操作系统还要把内核态切换回用户态,执行完后,再返回内核态呢?内核态还有更高权限,可以访问所以代码,这样做岂不是多此一举?首先我们要知道,自定义捕捉,是用户提供的,你能保证用户提供的代码是安全吗?如果是恶意代码呢?并且内核态权限高,用内核态的身份去执行这段不知道安全性的代码,风险会不会无限大呢?所以这样做其实是为了保护操作系统。

 

sigaction(信号,新动作,旧动作)捕获信号,执行动作

struct sigaction act,oact 创建动作

act.sa_handler=handler:自定义动作

                         SIG_IGN:忽略

                         SIG_DFL:默认

act.sa_flays=0;默认为0;

 

当一个被捕捉的信号,在执行处理动作时,会自动屏蔽该信号,直到执行动作结束,才会把该信号从信号屏蔽字解开,若在执行动作时,还要把屏蔽其他时,可以 sigaddset(&act.sa_mask,信号) 会在执行这个信号递达自动屏蔽指定信号

 

子进程退出时,暂停,继续会自动发送SIGCHLD 17号信号

 

作者:lemon-Breeze原文地址:https://www.cnblogs.com/LonelyMoNan/p/16933028.html

%s 个评论

要回复文章请先登录注册