Linux进程信号(4)(捕捉信号)(几种中断以及用户态和内核态)
• main函数调⽤insert函数向⼀个链表head中插⼊节点node1,插⼊操作分为两步,刚做完第⼀步的时候,因为硬件中断使进程切换到内核,再次回⽤⼾态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调⽤insert函数向同⼀个链表head中插⼊节点node2,插⼊操作的两步都做完之后从sighandler返回内核态,再次回到⽤⼾态就从main函数调⽤的ins
1.信号捕捉的流程

如果信号的处理动作是⽤⼾⾃定义函数,在信号递达时就调⽤这个函数,这称为捕捉信号。
由于信号处理函数的代码是在⽤⼾空间的,处理过程⽐较复杂,举例如下:
• ⽤⼾程序注册了SIGQUIT 信号的处理函数sighandler
• 当前正在执⾏main 函数,这时发⽣中断或异常切换到内核态。
• 在中断处理完毕后要返回⽤⼾态的main 函数之前检查到有信号
• 内核决定返回⽤⼾态后不是恢复数,sighandler 和SIGQUIT 递达。main 函数的上下⽂继续执⾏,⽽是执⾏sighandler 函main 函数使⽤不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是两个独⽴的控制流程。
• sighandler 函数返回后⾃动执⾏特殊的系统调⽤sigreturn 再次进⼊内核态。
• 如果没有新的信号要递达,这次再返回⽤⼾态就是恢复main 函数的上下⽂继续执⾏了。

2.sigaction
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
• sigaction函数可以读取和修改与指定信号相关联的处理动作。调⽤成功则返回0,出错则返回-1。signo是指定信号的编号。若act指针⾮空,则根据act修改该信号的处理动作。若oact指针⾮空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:
• 将sa_handler赋值为常数SIG_IGN传给sigaction表⽰忽略信号,赋值为常数SIG_DFL表⽰执⾏系统默认动作,赋值为⼀个函数指针表⽰⽤⾃定义函数捕捉信号,或者说向内核注册了⼀个信号处理函数,该函数返回值为void,可以带⼀个int参数,通过参数可以得知当前信号的编号,这样就可以⽤同⼀个函数处理多种信号。显然,这也是⼀个回调函数,不是被main函数调⽤,⽽是被系统所调⽤。
当某个信号的处理函数被调⽤时,内核⾃动将当前信号加⼊进程的信号屏蔽字,当信号处理函数返回时⾃动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产⽣,那么它会被阻塞到当前处理结束为⽌。如果在调⽤信号处理函数时,除了当前信号被⾃动屏蔽之外,还希望⾃动屏蔽另外⼀些信号,则⽤sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时⾃动恢复原来的信号屏蔽字。
3.硬件中断

• 中断向量表就是操作系统的⼀部分,启动就加载到内存中了
• 通过外部硬件中断,操作系统就不需要对外设进⾏任何周期性的检测或者轮询
• 由外部设备触发的,中断系统运⾏流程,叫做硬件中断
4.时钟中断
// Linux
内核
0.11
// main.c
sched_init(); //
调度程序初始化
(
(tr, ldtr)
kernel/sched.c
)
//
调度程序的初始化⼦程序。
void sched_init(void)
{
...
set_intr_gate(
0x20
, &timer_interrupt);
//
修改中断控制器屏蔽码,允许时钟中断。
outb(inb_p(0x21) & ~0x01, 0x21);
//
设置系统调⽤中断⻔。
set_system_gate(0x80, &system_call);
...
}
// system_call.s
_timer_interrupt:
...
//
调度⼊⼝
void do_timer(long cpl)
;// do_timer(CPL)
执⾏任务切换、计时等⼯作,在
kernel/shched.c,305
⾏实现。
call _do_timer ;// 'do_timer(long CPL)' does everything from
4.1操作系统的本质
操作系统的本质是一个死循环
{
void main(void) /*
这⾥确实是void,并没错。 */
/* 在startup 程序(head.s)中就是这样假设的。 */
...
/**
注意!!
对于任何其它的任务,'pause()'将意味着我们必须等待收到⼀个信号才会返回就绪运⾏态,但任务(task0)是唯⼀的意外情况(参⻅'schedule()'),因为任
*任务0 在任何空闲时间⾥都会被激活(当没有其它任务在运⾏时), * 因此对于任务0'pause()'仅意味着我们返回来查看是否有其它任务可以运⾏,如果没有的话我们就回到这⾥,⼀直循环执⾏'pause()'。
*/
for (;;)
pause();
} // end main
• 这样,操作系统,就可以在硬件时钟的推动下,⾃动调度了.
• 所以,什么是时间⽚?CPU为什么会有主频?为什么主频越快,CPU越快?主频可以作为OS调度执⾏速度的参考之一
5.软中断
• 上述外部硬件中断,需要硬件设备触发。
• 有没有可能,因为软件原因,也触发上⾯的逻辑?有!
• 为了让操作系统⽀持进⾏系统调⽤,CPU也设计了对应的汇编指令(int或者syscall),可以让CPU内部触发中断逻辑。
所以:

• ⽤⼾层怎么把系统调⽤号给操作系统?-寄存器(⽐如EAX)
• 操作系统怎么把返回值给⽤⼾?-寄存器或者⽤⼾传⼊的缓冲区地址
• 系统调⽤的过程,其实就是先int0x80、syscall陷⼊内核,本质就是触发软中断,CPU就会⾃动执
⾏系统调⽤的处理⽅法,⽽这个⽅法会根据系统调⽤号,⾃动查表,执⾏对应的⽅法
• 系统调⽤号的本质:数组下标
• 可是为什么我们⽤的系统调⽤,从来没有⻅过什么nt 0x80 或者syscall 呢?都是直接调⽤上层的函数的啊?
• 那是因为Linux的gnuC标准库,给我们把⼏乎所有的系统调⽤全部封装了。
• #define SYS_ify(syscall_name) __NR_##syscall_name :是⼀个宏定义,⽤于将系统调⽤的名称转换为对应的系统调⽤号。⽐如:SYS_ify(open) 会被展开为__NR_open
• ⽽系统调⽤号,不是__NR_open glibc 提供的,是内核提供的,内核提供系统调⽤⼊⼝函数syscall ,或者直接提供汇编级别软中断命令man 2 int or syscall ,并提供对应的头⽂件或者开发⼊⼝,让上层语⾔的设计者使⽤系统调⽤号,完成系统调⽤过程

• 缺⻚中断?内存碎⽚处理?除零野指针错误?这些问题,全部都会被转换成为CPU内部的软中断,然后⾛中断处理例程,完成所有处理。有的是进⾏申请内存,填充⻚表,进⾏映射的。有的是⽤来处理内存碎⽚的,有的是⽤来给⽬标进⾏发送信号,杀掉进程等等。
• 操作系统就是躺在中断处理例程上的代码块!
• CPU内部的软中断,⽐如int 0x80或者syscall,我们叫做陷阱
• CPU内部的软中断,⽐如除零/野指针等,我们叫做异常。
6.用户态和内核态

6.1结论
• 操作系统⽆论怎么切换进程,都能找到同⼀个操作系统!换句话说操作系统系统调⽤⽅法的执⾏,是在进程的地址空间中执⾏的!
• 关于特权级别,涉及到段,段描述符,段选择⼦,DPL,CPL,RPL等概念,⽽现在芯⽚为了保证兼容性,已经⾮常复杂了,进⽽导致OS也必须得照顾它的复杂性
• ⽤⼾态就是执⾏⽤⼾[0,3]GB时所处的状态
• 内核态就是执⾏内核[3,4]GB时所处的状态
• 区分就是按照CPU内的CPL决定,CPL的全称是Current Privilege Level,即当前特权级别。
• ⼀般执⾏int 0x80 或者syscall 软中断,CPL会在校验之后⾃动变更
• 这样会不会不安全??
7.可重入函数


• main函数调⽤insert函数向⼀个链表head中插⼊节点node1,插⼊操作分为两步,刚做完第⼀步的时候,因为硬件中断使进程切换到内核,再次回⽤⼾态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调⽤insert函数向同⼀个链表head中插⼊节点node2,插⼊操作的两步都做完之后从sighandler返回内核态,再次回到⽤⼾态就从main函数调⽤的insert函数中继续往下执⾏,先前做第⼀步之后被打断,现在继续做完第⼆步。结果是,main函数和sighandler先后向链表中插⼊两个节点,⽽最后只有⼀个节点真正插⼊链表中了。
• 像上例这样,insert函数被不同的控制流程调⽤,有可能在第⼀次调⽤还没返回时就再次进⼊该函数,这称为重⼊,insert函数访问⼀个全局链表,有可能因为重⼊⽽造成错乱,像这样的函数称为不可重⼊函数,反之,如果⼀个函数只访问⾃⼰的局部变量或参数则称为可重⼊(Reentrant)函数。想⼀下,为什么两个不同的控制流程调⽤同⼀个函数,访问它的同⼀个局部变量或参数就不会造成错乱?如果⼀个函数符合以下条件之⼀则是不可重⼊的:
• 调⽤了malloc或free,因为malloc也是⽤全局链表来管理堆的。
• 调⽤了标准I/O库函数。标准I/O库的很多实现都以不可重⼊的⽅式使⽤全局数据结构。
8. volatile


volatile 作⽤:保持内存的可⻅性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进⾏操作
更多推荐


所有评论(0)