Linux---信号
1 信号快速认识
1-1 生活角度的信号
闹钟,红绿灯,上课铃声,狼烟,肚子叫,敲门声 ...... 这个时候人就相当于进程
1-2 概念角度的信号
信号就是中断我们人正在做的事情,是一种事情的异步通知机制。是一种给进程发送的,用来进行事件异步通知的机制。
举例理解同步异步:
老师,让张三去帮我拿东西,如果我们自习一会,等张三回来再讲课就是同步机制。如果我们继续上课,不等张三就是异步。
信号的产生,相当于进程的运行,是异步的。信号是发送给进程的
1-3 信号的基本结论
从上面两个角度我们可以得到关于信号的一些基本结论
1 信号处理,进程在没有产生的时候,早就知道信号该如何处理了
2 信号的处理,不是立即处理,而是可以等一会在处理,合适的时候,进行信号处理
3 人能识别信号,是因为被“提前”教育过的,进程也是如此,OS程序员设计的进程,进程早已经内置了对于信号的识别和处理方式
4 信号源非常多 -> 给进程产生的,信号源,也非常多
1-4 信号概念
信号是进程之间事件异步通知的一种方式,属于软中断。
1-4-1 查看信号 


1-4-2 信号处理
信号处理有三种方式:
1 默认处理动作
2 自定义信号处理,自定义捕捉方法,一般是使用系统调用接口

3 忽略处理
2 信号的产生
当前阶段:

2-1 通过终端按键产生信号
2-1-1 基本操作
例如ctrl+c 是给目标进程发送信号。相当一部分信号的处理动作,就是让自己终止

2 代码示例验证
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
void Catch(int signal)
{
cout<<"捕捉到一个信号:"<<signal<<endl;
}
int main()
{
signal(SIGINT,Catch);
while(true)
{
sleep(1);
cout<<"我是进程"<<endl;
}
return 0;
}

2-1-2 理解OS如何得知键盘有数据

通过键盘产生信号,发给目标进程,什么是目标进程呢?
目标进程就是前台进程,键盘产生的信号,只能交给前台进程。
进程分类:
1 前台进程 。/XXX ---前台进程,命令行shell进程就是一个前台进程,能从键盘中获取输入
2 后台进程。 /XXX & ---后台进程,无法从标准输入比如键盘中获取输入内容

注意:
前台进程就只能有一个,但是后台进程可以有多个。为什么? 因为前台进程的本质,就是从键盘获取数据,但是输入就只有一个,所以规定前台进程只能有一个。
补充命令:
jobs--查看所以后台任务
fb --- 任务号,特定进程,提到前台
ctrl+z 进程切换到后台
bg 任务号:让后台任务恢复运行
2-1-3 初步理解信号起源
2-2 使用系统命令产生信号
kill -信号 进程pid
2-3 调用系统调用产生信号
2-3-1 kill 函数
kill 命令是调用 kill 函数实现的。 kill 函数可以给⼀个指定的进程发送指定的信号。
NAME
kill - send signal to a process
SYNOPSIS
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
RETURN VALUE
On success (at least one signal was sent), zero is returned. On error,
-1 is returned, and errno is set appropriately.
参数:
pid--传入目标进程pid
sig -- 传入要传入的信号标号
2-3-2 raise 函数
raise 函数可以给当前进程发送指定的信号(自己给自己发信号)。
NAME
raise - send a signal to the caller
SYNOPSIS
#include <signal.h>
int raise(int sig);
RETURN VALUE
raise() returns 0 on success, and nonzero for failure.
参数:
sig -- 传入要传入的信号标号
2-3-3 abort
abort 函数使当前进程接收到信号而异常终止。
NAME
abort - cause abnormal process termination
SYNOPSIS
#include <stdlib.h>
void abort(void);
RETURN VALUE
The abort() function never returns.
// 就像exit函数⼀样,abort函数总是会成功的,所以没有返回值。
2-4 由软件条件产生信号
2-4-1 如何理解软件条件
2-4-1 常见的软件条件--时钟
NAME
alarm - set an alarm clock for delivery of a signal
SYNOPSIS
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
RETURN VALUE
alarm() returns the number of seconds remaining until any previously
scheduled alarm was due to be delivered, or zero if there was no previ‐
ously scheduled alarm.

2-4-3 如何简单快速理解系统闹钟
struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_t_base_s *base;
};
2-5 硬件异常产生信号
2-5-1 硬件异常是什么
2-5-2 如何理解硬件异常 
操作系统如何识别到进程出错了??CPU 在运行时如果出现错误会触发异常(CPU 内部异常)。当异常发生,CPU 硬件会自动产生对应的异常编号,并将当前程序的寄存器上下文压入内核栈;操作系统的异常处理程序根据异常编号判断错误类型,后续会把进程的上下文保存在task_struct内的thread_struct字段中。
其实在CPU中有⼀些控制和状态寄存器,主要用于控制处理器的操作,通常由操作系统代码使用。状态寄存器可以简单理解为一个位图,对应着一些状态标记位、溢出标记位。OS 会检测是否存在异常状态,有异常存在就会调用对应的异常处理方法。
2-5-3 子进程退出core dump
2-5-4 Core Dump
$ ulimit -c 1024
2-5-5 核心转储
Core状态指的是,会在当前路径下形成一个文件进程异常退出的时候,进程在内存中的核心数据,从内存拷贝到磁盘。形成一个文件,这个过程称为 ---- 核心转储。
核心转储支持debug,开启了 core dump ,直接运行崩溃,gdb,core-file core ,直接帮我们定位到出错行 --- 事后调试

这一个core dump 标志位表示了是否发生了核心转储
3 保存信号
当前阶段:

3-1 信号其他相关常见概念
3-2 在内核中的表示

三张表!!!
block表是位图用来记录对应信号是否阻塞。比特位的位置:表示受到第几个信号。比特位的内容:表示是否阻塞
pending表保存收到的信号的位图。比特位的位置:表示受到第几个信号。比特位的内容:表示是否受到
判断一个信号是否要执行对应方法:pending对应的比特位 &(~block对应比特位)
handler表示函数指针数组,存放对应信号发生的时候调用的方法。
sighandler_t handler[31]。数组下标就是信号编号
信号的内核数据结构
// 内核结构 2.6.18
struct task_struct {
...
/* signal handlers */
struct sighand_struct *sighand;
sigset_t blocked
struct sigpending pending;
...
}
struct sighand_struct {
atomic_t count;
struct k_sigaction action[_NSIG]; // #define _NSIG 64
spinlock_t siglock;
};
struct __new_sigaction {
__sighandler_t sa_handler;
unsigned long sa_flags;
void (*sa_restorer)(void); /* Not used by Linux/SPARC */
__new_sigset_t sa_mask;
};
struct k_sigaction {
struct __new_sigaction sa;
void __user *ka_restorer;
};
/* Type of a signal handler. */
typedef void (*__sighandler_t)(int);
struct sigpending {
struct list_head list;
sigset_t signal;
};
3-3 sigset_t
3-4 信号集操作函数
3-4-1 sigset_t 操作函数
#include <signal.h>
int sigemptyset(sigset_t *set);//将位图清空
int sigfillset(sigset_t *set);//将位图全部置为1
int sigaddset(sigset_t *set, int signo);//设置位图的某一位即某个信号
int sigdelset(sigset_t *set, int signo);//清空位图的某一位即某个信号
int sigismember(const sigset_t *set, int signo);//查看某个信号是否在信号集内
3-4-2 sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
参数:
how:设置block表更改模式,是添加,解除还是重新设置信号屏蔽自
set:输入型参数,输入你要设置的信号屏蔽字
oset:输出型参数,保存信号屏蔽字的设置
返回值:
若成功则为0,若出错则为-1

3-4-2 sigpending
#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。
调⽤成功则返回0,出错则返回-1
注意:
sigpending不可以设置pending表,因为pending表是否被设置和我们上面说的信号的产生所相关。
4 信号处理
当前阶段:

4-1 信号捕捉的流程
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
什么是合适的时候处理信号?进程从内核态,返回到用户态的时候做信号检查
什么时候做进程从内核态,返回到用户态呢?在进程调用系统调用,或者进程切换的时候。这个在后面的穿插话题详细解释
如果信号处理动作是默认呢?忽略呢?默认和忽略处理动作是由系统定义的,不涉及到用户态和内核态的切换,所以比自定义捕捉要简单,所以我们只需要详细了解自定义捕捉即可
所以我们在执行自定义方法(用户写的) -> OS也要在做身份切换 -> 必须要以用户的身份执行

4-2 操作系统是怎么运行的
4-2-1 硬件中断

1 中断向量表就是操作系统的⼀部分,启动就加载到内存中了,中断向量表本质就是一个函数指针数组,下标就是中断号
2 通过外部硬件中断,操作系统就不需要对外设进行任何周期性的检测或者轮询,当那个外部设备准备好了,可以通过中断让CPU知道,
3 CPU会根据中断号,执行已经准备好的中断处理方法,例如读取键盘数据
4 注意寄存器的概念,外设也有,就是CPU就是通过外设的寄存器来对外设进行控制
5 由外部设备触发的,中断运行流程,叫做硬件中断

//Linux内核0.11源码
void trap_init(void)
{
int i;
set_trap_gate(0,÷_error);// 设置除操作出错的中断向量值。以下雷同。
set_trap_gate(1,&debug);
set_trap_gate(2,&nmi);
set_system_gate(3,&int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_trap_gate(14,&page_fault);
set_trap_gate(15,&reserved);
set_trap_gate(16,&coprocessor_error);
// 下⾯将int17-48 的陷阱⻔先均设置为reserved,以后每个硬件初始化时会重新设置⾃⼰的陷阱⻔。
for (i=17;i<48;i++)
set_trap_gate(i,&reserved);
set_trap_gate(45,&irq13);// 设置协处理器的陷阱⻔。
outb_p(inb_p(0x21)&0xfb,0x21);// 允许主8259A 芯⽚的IRQ2 中断请求。
outb(inb_p(0xA1)&0xdf,0xA1);// 允许从8259A 芯⽚的IRQ13 中断请求。
set_trap_gate(39,¶llel_interrupt);// 设置并⾏⼝的陷阱⻔。
}
void rs_init (void)
{
set_intr_gate (0x24, rs1_interrupt); // 设置串⾏⼝1 的中断⻔向量(硬件IRQ4信号)。
set_intr_gate (0x23, rs2_interrupt); // 设置串⾏⼝2 的中断⻔向量(硬件IRQ3信号)。
init (tty_table[1].read_q.data); // 初始化串⾏⼝1(.data 是端⼝号)。
init (tty_table[2].read_q.data); // 初始化串⾏⼝2。
outb (inb_p (0x21) & 0xE7, 0x21); // 允许主8259A 芯⽚的IRQ3,IRQ4 中断信号请求。
}
你可以将信号和中断类比:
发中断 -- 发信号
保存中断号 -- 记录信号
中断号 -- 信号编号
处理中断 -- 处理信号,自定义捕捉
4-3-2 时钟中断
当没有中断来的时候,OS在做什么??什么都没有做!!OS是暂停的!!

外部设备可以触发硬件中断,但是这个是需要用户或者设备自己触发,有没有自己可以定期触发的设备?有就是时钟源。时钟源以固定的,特定的频率,向CPU发送特定的中断!!
时钟源每次向CPU发送一次特定的中断,CPU就执行中断服务函数:进程调度,所以这时候OS就会陷入内核进行对于这个进程,执行检查是否时间片耗尽,是否进行调度的调度算法,然后再返回用户态,并且在返回用户态之前进行信号检查
进程调度需要先检查当前进程的时间片有没有耗尽。那么时间片又是什么呢?时间片本质就是进程PCB上的一个计数器指针,由OS分配,每次时钟中断的时候就++。通过给这个计数器设置来计算这个进程运行的时候,当是时间片耗尽再进行进程调度。所以操作系统,就在硬件时钟中断的驱动下,进行调度了。
所以操作系统是什么?操作系统,就是基于中断,进行工作的软件
// Linux 内核0.11
// main.c
sched_init(); // 调度程序初始化(加载了任务0 的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:
...
;// do_timer(CPL)执⾏任务切换、计时等⼯作,在kernel/shched.c,305 ⾏实现。
call _do_timer ;// 'do_timer(long CPL)' does everything from
// 调度⼊⼝
void do_timer(long cpl)
{
...
schedule();
}
void schedule(void)
{
...
switch_to(next); // 切换到任务号为next 的任务,并运⾏之。
}
4-2-3 死循环
void main(void) /* 这⾥确实是void,并没错。 */
{
/* 在startup 程序(head.s)中就是这样假设的。 */
...
/*
* 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到⼀个信号才会返
* 回就绪运⾏态,但任务0(task0)是唯⼀的意外情况(参⻅'schedule()'),因为任
* 务0 在任何空闲时间⾥都会被激活(当没有其它任务在运⾏时),
* 因此对于任务0'pause()'仅意味着我们返回来查看是否有其它任务可以运⾏,如果没
* 有的话我们就回到这⾥,⼀直循环执⾏'pause()'。
*/
for (;;)
pause();
} // end main
4-2-4 软中断

// sys.h
// 系统调⽤函数指针表。⽤于系统调⽤中断处理程序(int 0x80),作为跳转表。
extern int sys_setup (); // 系统启动初始化设置函数。 (kernel/blk_drv/hd.c,71)
extern int sys_exit (); // 程序退出。 (kernel/exit.c, 137)
extern int sys_fork (); // 创建进程。 (kernel/system_call.s, 208)
extern int sys_read (); // 读⽂件。 (fs/read_write.c, 55)
extern int sys_write (); // 写⽂件。 (fs/read_write.c, 83)
extern int sys_open (); // 打开⽂件。 (fs/open.c, 138)
extern int sys_close (); // 关闭⽂件。 (fs/open.c, 192)
extern int sys_waitpid (); // 等待进程终⽌。 (kernel/exit.c, 142)
extern int sys_creat (); // 创建⽂件。 (fs/open.c, 187)
extern int sys_link (); // 创建⼀个⽂件的硬连接。 (fs/namei.c, 721)
extern int sys_unlink (); // 删除⼀个⽂件名(或删除⽂件)。 (fs/namei.c, 663)
extern int sys_execve (); // 执⾏程序。 (kernel/system_call.s, 200)
extern int sys_chdir (); // 更改当前⽬录。 (fs/open.c, 75)
extern int sys_time (); // 取当前时间。 (kernel/sys.c, 102)
extern int sys_mknod (); // 建⽴块/字符特殊⽂件。 (fs/namei.c, 412)
extern int sys_chmod (); // 修改⽂件属性。 (fs/open.c, 105)
extern int sys_chown (); // 修改⽂件宿主和所属组。 (fs/open.c, 121)
extern int sys_break (); // (-kernel/sys.c, 21)
extern int sys_stat (); // 使⽤路径名取⽂件的状态信息。 (fs/stat.c, 36)
extern int sys_lseek (); // 重新定位读/写⽂件偏移。 (fs/read_write.c, 25)
extern int sys_getpid (); // 取进程id。 (kernel/sched.c, 348)
extern int sys_mount (); // 安装⽂件系统。 (fs/super.c, 200)
extern int sys_umount (); // 卸载⽂件系统。 (fs/super.c, 167)
extern int sys_setuid (); // 设置进程⽤⼾id。 (kernel/sys.c, 143)
extern int sys_getuid (); // 取进程⽤⼾id。 (kernel/sched.c, 358)
extern int sys_stime (); // 设置系统时间⽇期。 (-kernel/sys.c, 148)
extern int sys_ptrace (); // 程序调试。 (-kernel/sys.c, 26)
extern int sys_alarm (); // 设置报警。 (kernel/sched.c, 338)
extern int sys_fstat (); // 使⽤⽂件句柄取⽂件的状态信息。(fs/stat.c, 47)
extern int sys_pause (); // 暂停进程运⾏。 (kernel/sched.c, 144)
extern int sys_utime (); // 改变⽂件的访问和修改时间。 (fs/open.c, 24)
extern int sys_stty (); // 修改终端⾏设置。 (-kernel/sys.c, 31)
extern int sys_gtty (); // 取终端⾏设置信息。 (-kernel/sys.c, 36)
extern int sys_access (); // 检查⽤⼾对⼀个⽂件的访问权限。(fs/open.c, 47)
extern int sys_nice (); // 设置进程执⾏优先权。 (kernel/sched.c, 378)
extern int sys_ftime (); // 取⽇期和时间。 (-kernel/sys.c,16)
extern int sys_sync (); // 同步⾼速缓冲与设备中数据。 (fs/buffer.c, 44)
extern int sys_kill (); // 终⽌⼀个进程。 (kernel/exit.c, 60)
extern int sys_rename (); // 更改⽂件名。 (-kernel/sys.c, 41)
extern int sys_mkdir (); // 创建⽬录。 (fs/namei.c, 463)
extern int sys_rmdir (); // 删除⽬录。 (fs/namei.c, 587)
extern int sys_dup (); // 复制⽂件句柄。 (fs/fcntl.c, 42)
extern int sys_pipe (); // 创建管道。 (fs/pipe.c, 71)
extern int sys_times (); // 取运⾏时间。 (kernel/sys.c, 156)
extern int sys_prof (); // 程序执⾏时间区域。 (-kernel/sys.c, 46)
extern int sys_brk (); // 修改数据段⻓度。 (kernel/sys.c, 168)
extern int sys_setgid (); // 设置进程组id。 (kernel/sys.c, 72)
extern int sys_getgid (); // 取进程组id。 (kernel/sched.c, 368)
extern int sys_signal (); // 信号处理。 (kernel/signal.c, 48)
extern int sys_geteuid (); // 取进程有效⽤⼾id。 (kenrl/sched.c, 363)
extern int sys_getegid (); // 取进程有效组id。 (kenrl/sched.c, 373)
extern int sys_acct (); // 进程记帐。 (-kernel/sys.c, 77)
extern int sys_phys (); // (-kernel/sys.c, 82)
extern int sys_lock (); // (-kernel/sys.c, 87)
extern int sys_ioctl (); // 设备控制。 (fs/ioctl.c, 30)
extern int sys_fcntl (); // ⽂件句柄操作。 (fs/fcntl.c, 47)
extern int sys_mpx (); // (-kernel/sys.c, 92)
extern int sys_setpgid (); // 设置进程组id。 (kernel/sys.c, 181)
extern int sys_ulimit (); // (-kernel/sys.c, 97)
extern int sys_uname (); // 显⽰系统信息。 (kernel/sys.c, 216)
extern int sys_umask (); // 取默认⽂件创建属性码。 (kernel/sys.c, 230)
extern int sys_chroot (); // 改变根系统。 (fs/open.c, 90)
extern int sys_ustat (); // 取⽂件系统信息。 (fs/open.c, 19)
extern int sys_dup2 (); // 复制⽂件句柄。 (fs/fcntl.c, 36)
extern int sys_getppid (); // 取⽗进程id。 (kernel/sched.c, 353)
extern int sys_getpgrp (); // 取进程组id,等于getpgid(0)。(kernel/sys.c, 201)
extern int sys_setsid (); // 在新会话中运⾏程序。 (kernel/sys.c, 206)
extern int sys_sigaction (); // 改变信号处理过程。 (kernel/signal.c, 63)
extern int sys_sgetmask (); // 取信号屏蔽码。 (kernel/signal.c, 15)
extern int sys_ssetmask (); // 设置信号屏蔽码。 (kernel/signal.c, 20)
extern int sys_setreuid (); // 设置真实与/或有效⽤⼾id。 (kernel/sys.c,118)
extern int sys_setregid (); // 设置真实与/或有效组id。 (kernel/sys.c, 51)
// 系统调⽤函数指针表。⽤于系统调⽤中断处理程序(int 0x80),作为跳转表。
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid, sys_setregid
};
// 调度程序的初始化⼦程序。
void sched_init(void)
{
...
// 设置系统调⽤中断⻔。
set_system_gate(0x80, &system_call);
}
_system_call:
cmp eax,nr_system_calls-1 ;// 调⽤号如果超出范围的话就在eax 中置-1 并退出。
ja bad_sys_call
push ds ;// 保存原段寄存器值。
push es
push fs
push edx ;// ebx,ecx,edx 中放着系统调⽤相应的C 语⾔函数的调⽤参数。
push ecx ;// push %ebx,%ecx,%edx as parameters
push ebx ;// to the system call
mov edx,10h ;// set up ds,es to kernel space
mov ds,dx ;// ds,es 指向内核数据段(全局描述符表中数据段描述符)。
mov es,dx
mov edx,17h ;// fs points to local data space
mov fs,dx ;// fs 指向局部数据段(局部描述符表中数据段描述符)。
;// 下⾯这句操作数的含义是:调⽤地址 = _sys_call_table + %eax * 4。参⻅列表后的说明。
;// 对应的C 程序中的sys_call_table 在include/linux/sys.h 中,其中定义了⼀个包括72个
;// 系统调⽤C 处理函数的地址数组表。
call [_sys_call_table+eax*4]
push eax ;// 把系统调⽤号⼊栈。
mov eax,_current ;// 取当前任务(进程)数据结构地址??eax。
;// 下⾯97-100 ⾏查看当前任务的运⾏状态。如果不在就绪状态(state 不等于0)就去执⾏调度程序。
;// 如果该任务在就绪状态但counter[??]值等于0,则也去执⾏调度程序。
cmp dword ptr [state+eax],0 ;// state
jne reschedule
cmp dword ptr [counter+eax],0 ;// counter
je reschedule
;// 以下这段代码执⾏从系统调⽤C 函数返回后,对信号量进⾏识别处理。
ret_from_sys_call:

# define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
({ \
unsigned long int resultvar; \
LOAD_ARGS_##nr (args) \
LOAD_REGS_##nr \
asm volatile ( \
"syscall\n\t" \
: "=a" (resultvar) \
: "0" (name) ASM_ARGS_##nr : "memory", "cc", "r11", "cx"); \
(long int) resultvar; })
每次在调用封装函数时,在这个封装函数开头会先从寄存器中读取系统调用号,然后再让OS调用 int 或者 syscall 这个指令集陷入内核,然后执行函数,所以,OS不提供任何系统掉i用号的接口!!OS只提供系统调用号
4-2-5 缺页中断?内存碎片处理?除零野指针错误?
void trap_init(void)
{
int i;
set_trap_gate(0,÷_error);// 设置除操作出错的中断向量值。以下雷同。
set_trap_gate(1,&debug);
set_trap_gate(2,&nmi);
set_system_gate(3,&int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_trap_gate(14,&page_fault);
set_trap_gate(15,&reserved);
set_trap_gate(16,&coprocessor_error);
// 下⾯将int17-48 的陷阱⻔先均设置为reserved,以后每个硬件初始化时会重新设置⾃⼰的陷阱⻔。
for (i=17;i<48;i++)
·set_trap_gate(i,&reserved);
set_trap_gate(45,&irq13);// 设置协处理器的陷阱⻔。
outb_p(inb_p(0x21)&0xfb,0x21);// 允许主8259A 芯⽚的IRQ2 中断请求。
outb(inb_p(0xA1)&0xdf,0xA1);// 允许从8259A 芯⽚的IRQ13 中断请求。
set_trap_gate(39,¶llel_interrupt);// 设置并⾏⼝的陷阱⻔。
}
4-3 sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction*oact);
参数:
signum:输入对应的信号编号
act:输入型参数,传入 struct sigaction 设置对应信号的自定义捕捉方法还有配置信息
oact:输出型参数,传出对应信号的老的配置信息
struct sigaction {
void (*sa_handler)(int);//自定义捕捉方法
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;//再调用某个信号的处理函数的时候设置信号屏蔽字
void (*sa_restorer)(void);
};
返回值:
0:成功返回
-1:失败返回
4-4 如何理解内核态和用户态

操作系统也是软件,一定也在内存!系统只有一份!所有进程共享,所以操作系统不管怎么切换进程,都能找到同一个操作系统!换句话说操作系统的系统调用方法的执行,是在进程地址空间中执行的
因为进程地址空间,所以用户和进程,都在同一个[0,4GB]地址空间上了,如果用户,随便拿一个虚拟地址[3,4GB],用户也不可以访问内核的代码和数据。OS为了保护自己,不相信任何人!!必须采用系统调用的方式在进程地址空间内部进行跳转访问
用户态:以用户的身份,只能访问自己的[0,3GB]这个范围的进程地址空间的虚拟地址
内核态:以内核身份,允许你通过系统调用的方式,访问OS[3,4GB]
在系统中,用户或者OS自己,怎么知道自己是处于内核态还是用户态?通过CPU中的CPL寄存器来表示,CPL为0表示内核态,CPL为3表示为用户态
5 可重入函数
6 volatile
#include <stdio.h>
#include <signal.h>
int flag = 0; //volatile int flag = 0;
void handler(int sig)
{
printf("chage flag 0 to 1\n");
flag = 1;
}
int main()
{
signal(2, handler);
while(!flag);
printf("process quit normal\n");
return 0;
}
在上述代码的情况下,main里面没有对于flag进行修改,在编译器优化级别较高的时候,就会优化这一个过程,因为flag只需要访问不做修改,所以优化后,每次CPU都不从内存中读取flag的值,而是读取flag储存在寄存器中的值,如果flag一直没有修改那不会有问题,但是当这个进程接受到信号的时候,并且执行的是自定义处理方法,在自定义方法中,对于flag进行修改了,但是main里没有修改,因为CPU还是会读寄存器的值,所以导致了程序死循环,这种情况是寄存器覆盖了进程看到变量的真实情况,内存不可见了!!
为了保证内存的可见性,所以我们可以在这个变量前面加上volatile关键字,让CPU每次都从内存中搬运数据,保证内存可见性。

7 SIGCHLD信号
子进程在终止的时候会给父进程发送SIGCHILD信号,这个信号处理动作是SIG_DFL默认忽略这个信号,但是我们可以自定义捕捉方法,可以对子进程进行回收,使得父进程不用一直wait解放了父进程
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler(int sig)
{
while(true) //回收多进程
{
pid_t n= waitpid(-1,nullptr,WNOHANG);//WNOHANG避免阻塞等待导致父进程卡死
if(n==0)
break;
else if(n<0)
{
std::cerr<<"waitpid error"<<std::endl;
break;
}
}
}
int main()
{
signal(SIGCHLD, handler);
pid_t cid;
if((cid = fork()) == 0)
{
//child
printf("child : %d\n", getpid());
sleep(3);
exit(1);
}
while(1){
printf("father proc is doing some thing!\n");
sleep(1);
}
return 0;
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)