#./XXX 前台进程
#./YYY& 后台进程
前台进程能从键盘获取标准输入,后台进程不能
但二者都可以向标准输出上打印
| jobs | 查看所有的后台任务 |
|---|---|
| fg+任务号 | 将特定的进程提到前台 |
| ctrl+z | 将进程切换到后台 |
| bg+任务号 | 让后台进程恢复运行 |
信号
1 kill
2 raise
task_struct内部维护了两个位图BitMap和一个函数指针数组
1 block表(阻塞信号集)
2 pending表(未决信号集)
3 handler表(信号处理函数表)
调用signal()时,底层其实就是在修改这个表
BLOCK
这里的sigset_t *set代表指向某个信号集变量的指针,int代表具体的编号
| sigemptyset(sigset_t *set) | 把信号集里所有的位都清零 |
| sigfillset(sigset_t *set) | 把信号集里所有的位置都置为1 |
| sigismember(const sigset_t *set, int signo) | 判断信号是否存在 |
| sigdelset(sigset_t *set, int signo) | 删除特定信号 |
这五个函数仅仅是在程序的内存中修改一个普通的局部变量,之后需要调用真正与内核沟通的系统调用(比如sigprocmask), 操作系统才会真正去修改block位图
how:
SIG_BLOCK:追加屏蔽
SIG_UNBLOCK:解除屏蔽
SIG_SETMASK:直接覆盖
set:
…如果把set传为NULL,这通常搭配第三个参数一起使用,用来纯查询当前系统屏蔽了哪些信号
oldset:
保存修改前的旧名单
如果不想保存,直接传NULL
PENDING
查询
返回当前进程的pending位图
修改
| 键盘 |
| 系统调用 |
| 系统命令 |
| 硬件异常 |
| 软件条件 |
硬件中断
OS是怎么知道外设上产生了数据的?
OS并不会以死循环的方式去不停地询问外设,而是通过硬件中断机制,而这也正是程序从“用户态”突然“陷入内核态”的常见原因之一
1 外设发送的电信号被中断控制器翻译成中断号并发送给CPU
2 在打断当前正在执行的任务前,CPU会把内部各个寄存器里的数据压入内核栈中保存
3 CPU用刚刚获取的中断号查询IDT(中断向量表),执行中断方法
4 之后,CPU会把保存在栈里的寄存器数据重新弹回CPU寄存器中,继续运行之前中断的任务
在众多硬件中断中,有一个特殊的存在:时钟中断
硬件时钟会以一个极其固定的频率向CPU发送一个硬件中断信号,所以,CPU会被迫定期地,不断地停下手头的工作,去查询IDT,跳转到“时钟中断处理程序”去执行一段代码
包括:
·扣除当前进程的时间片,决定是否剥夺CPU控制权
·处理所有的软件定时器
·更新系统全局时间
由此观之,硬件时钟和时钟中断堪称操作系统的心脏起搏器,是操作系统管理整个计算机的关键
软中断
硬件中断是外设强加给CPU的,而软中断是“正在运行的程序自己主动(或被动)触发的
| 系统调用(System Call) | C标准库会在底层执行一条特殊的汇编指令,人为地主动地制造一次中断,进行用户态与内核态的切换 |
| 内核中的”下半部“机制 | 在处理比如网络数据等需要大量时间的硬件中断时,CPU会先把数据从网卡寄存器拷贝到内存里,然后挂起一个软中断标志,把CPU释放去响应别的硬件,当系统准备返回用户态之前,检查软中断标志,再去执行滞留的任务 |
| 异常与陷阱 | 执行非法指令时,CPU硬件会发送内部中断信号,CPU查IDT,跳转到异常处理程序,由内核向进程发送信号,杀死程序并生成Core Dump文件 |
struct sigaction
1 sa_handler
函数指针,将写好的处理函数名赋值即可
2 sa_mask
信号集,设置在当前信号处理函数执行期间,需要额外屏蔽(阻塞)哪些信号
volatile:
向编译器发出明确指示:这个变量的值可能会被当前代码控制流之外的因素(如硬件中断,信号处理函数,其他线程)意外修改。不要对他的读取进行优化,即每次使用它时,必须强制去物理内存中读取最新值