目录
互斥的四个概念
编辑
查看当前的信号量
认识信号量接口
semget
semctl
理解IPC资源的管理
信号入门
生活角度的信号
技术应用角度的信号
注意
信号概念
用kill -l命令可以察看系统定义的信号列表
编辑
信号处理常见方式概览
信号产生
认识常用接口
signal
通过系统接口向进程发送信号
kill
raise
abort
由软件条件产生的信号
alarm
由硬件条件产生的信号
我们把大家都能看到的资源:公共资源
a.互斥:任何一个时刻,都只允许一个执行流在进行公共资源的访问——加锁
b.我们把任何一个时刻都只允许一个执行流在进行访问的共享资源,叫做临界资源
c.临界资源是要通过代码访问的,凡是访问临界资源的代码,叫做临界区
d.原子性
ipcs -s
信号量的获取
int semget(key_t key,int nsems,int semflg)
第一个参数: 键值,用于唯一标识一个信号量集。通常可以通过 ftok
函数生成。
第二个参数:信号量集,代表信号量的个数
第三个参数: 标志位,用于指定信号量的权限和行为选项。
得到一个信号量集标识符或创建一个信号量集对象
第一个参数: 信号量集标识符
第二个参数:是要操作的信号量在集合中的索引,通常为0,表示第一个信号量。
第三个参数:
cmd参数列表:
cmd 解释
IPC_STAT 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
IPC_SET 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
IPC_RMID 从内核中删除信号量集合
GETALL 从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中
GETNCNT 返回当前等待资源的进程个数
GETPID 返回最后一个执行系统调用semop()进程的PID
GETVAL 返回信号量集合内单个信号量的值
GETZCNT 返回当前等待100%资源利用的进程个数
SETALL 与GETALL正好相反
SETVAL 用联合体中val成员的值设置信号量集合中单个信号量的值
在shmctl,semctl,msqctl这些接口的描述里,都有对其数据结构的解释
操作系统用一个指针数组来统一管理,这也是一种多态的表现
--你在网上买了很多件商品,在等待不同商品快递的到来。但即便快递还没有到来,你也知道快递到了的时候应该怎么处理快递,也就是你能“识别快递”。
--当快递到达目的地了,你收到了快递到来的通知,但是你不一定要马上下楼取快递,也就是说取快递的行为并不是一定要立即执行,可以理解成在“在合适的时候去取”。
--在你收到快递到达的通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间内你并没有拿到快递,但是你知道快递已经到了,本质上是你“记住了有一个快递要去取”。
--当你时间合适,顺利拿到快递之后,就要开始处理快递了,而处理快递的方式有三种:1、执行默认动作(打开快递,使用商品)2、执行自定义动作(快递是帮别人买的,你要将快递交给他)3、忽略(拿到快递后,放在一边继续做自己的事)。
--快递到来的整个过程,对你来讲是异步的,你不能确定你的快递什么时候到。
1. 用户输入命令,在Shell下启动一个前台进程。 . 用户按下Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程 . 前台进程因为收到信号,进而引起进程退出
1. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程 结束就可以接受新的命令,启动新的进程。
用Ctrl+c只能杀死前台进程,后台进程用kill -9命令可以杀死
2. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生 的信号。
3. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行 到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步 (Asynchronous)的
信号是进程之间事件异步通知的一种方式,属于软中断
可选的处理动作有以下三种:
1. 忽略此信号。
2. 执行该信号的默认处理动作。
3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。
a.信号的产生对于进程来说是异步的
b.进程该如何处理对应产生的信号?记录在哪里?
--> 先描述,再组织——>怎么描述一个个信号,用什么数据结构管理这个信号
——> 01来描述信号,用位图这种数据结构管理这个信号
如0000 0000 0000 0000 0000 0001 0000 0000
c.所谓的发送信号,本质就是写入信号,直接修改特定进程的信号位图中的特定比特位0->1
d.数据内核结构,只能由OS进行修改,无论后面我们有多少种信号产生的方式,最终必须有OS来完成最后的发送过程
这里的第一个参数表示信号的编号,第二个参数则是一个函数指针,可以指向要执行的函数 (信号处理的方法)
这里2号信号的默认执行方法是杀死进程,用signal函数可以改变信号的处理方式,这里直接用了自定义的信号处理动作
这里提出一个小问题,在调用signal方法的时候,handler方法被调用了吗?
答案是并没有,他知识在操作系统内部更改了2号信号的处理动作,并没有调用handler
那么handler方法在什么时候才被调用呢?当2号信号产生的时候!
用signal(2,handler)来执行用户动作的自定义捕捉
这里两个信号都做捕捉
注:ctrl+c 2号信号 ctrl+\ 3号信号
以此类推,是不是可以对所有的信号都走自定义动作的设置
所有信号都自定义是不行的
这里9号信号之所以能够杀死进程,是因为9号信号是管理员信号,是不能被自定义动作的
写一个我们自己的kill命令
loop.cc
#include
#include
int main()
{
while (true)
{
std::cout << "我是一个进程,我正在运行 ..., pid: " << getpid() << std::endl;
sleep(1);
}
}
mykill.cc
#include
#include
#include
#include
#include
#include
#include
#include
#include
int count = 0;
void Usage(std::string proc)
{
std::cout << "\tUsage: \n\t";
std::cout << proc << " 信号编号 目标进程" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
int signo = atoi(argv[1]);
int target_id = atoi(argv[2]);
int n = kill(target_id,signo);
if(n!=0)
{
std::cerr<
int raise(int sig)
参数代表信号的编号
用法:“谁调用我,我就给谁发这个信号”
用来直接终止进程,谁调用就终止谁,在信号里是6号信号,这个接口信号在被自定义捕捉后依然会使进程退出,作用雷同与exit
这里的信号是14号信号SIGALRM
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后 响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就 是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数
我们平时在输入的时候,计算机怎么知道我们从键盘输入了数据呢?键盘是通过硬件中断的方式,通知系统,我们的键盘已经按下了
硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除 以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非 法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程
如除0这个运算操作(8号信号),还有野指针问题等(11号信号)