回顾:
UC成长之路1
UC成长之路2
UC成长之路3
UC成长之路4
UC成长之路5
UC成长之路6
UC成长之路7
UC成长之路8
UC成长之路9
UC成长之路10
一、信号的产生
信号的产生有三种方式
- 硬件:如Ctrl+c, Ctrl+\
- 使用Linux命令产生:
kill -signum pid
- 使用函数产生信号:kill(2), raise(3), alarm(2)
这里详细讲解alarm(2)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//功能:产生一个信号SINGNAL,递送给当前进程
//参数
//seconds:指定秒数,如果seconds==0没有新的闹钟使用,取消所有闹钟的闹钟设置
//返回值:原来闹钟的剩余时间
eg:举例说明alarm(2)的使用
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int i;
alarm(1);
//先測試自己的機器一秒能大概打印多少個數字
for(i=1;i>0;i++){//注意這裏不是死循環,當超出計算機表達的數字範圍後i會變爲負數
printf("%d\n", i);
}
return 0;
}
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int i;
//設置鬧鍾
alarm(5);
for(i=1;i<690000;i++){
printf("%d\n", i);
}
//取消鬧鍾,將原來的鬧鍾沒有執行的時間返回
int remain_time=alarm(0);
printf("remain_time=%d\n", remain_time);
return 0;
}
- 补充:函数pause(2)
#include <unistd.h>
int pause(void);
//功能:等待一个信号。让进程睡眠,直到一个信号到达
//返回值:只有信号抵达,信号处理函数返回以后才返回-1。errno被设置
eg:使用alarm(2)和pause(2)实现sleep(3)函数
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void doit(int n)
{
//不做任何操作
}
unsigned int t_sleep(unsigned int seconds)
{
//設置鬧鍾爲seconds秒
alarm(seconds);
//進程停下來等待信號的到達
pause();
//將剩餘沒有執行的時間返回
return (alarm(0));
}
int main(void)
{
signal(SIGALRM, doit);
while(1){
t_sleep(2);
printf("today...\n");
}
return 0;
}
二、信号阻塞和未决信号
- 工程师可以设置进程对某些信息的阻塞。信号抵达以后不对信号进行处理,结束对信号的阻塞以后,再对信号进程处理。
- 系统为我们封装了sigset_t类型,信号集类型。了解这个类型的相关操作
man 3 sigisemptyset
#include <signal.h>
int sigemptyset(sigset_t *set);
//功能:将信号集设置为空,所有的信号都被排除
//参数:
//set:指定要初始化的信号集
//返回值:成功返回0;错误返回-1
int sigfillset(sigset_t *set);
//功能:初始化信号集为满,包括所有的信号
//参数:
//set:指定要初始化的信号集
//返回值:成功返回0;错误返回-1
int sigaddset(sigset_t *set, int signum);
//功能:将信号集添加到信号集
//参数:
//set:指定信号集
//signum:指定了要添加到信号集的信号
//返回值:成功返回0;错误返回-1
int sigdelset(sigset_t *set, int signum);
//功能:从信号集中删除指定的信号
//参数:
//set:指定信号集
//signum:指定要删除的信号
//返回值:成功返回0;错误返回-1
int sigismember(const sigset_t *set, int signum);
//功能:测试信号是否是信号集的成员
//参数:
//set:指定信号集
//signum:指定要测试的信号
//返回值:如果信号是信号集的成员返回1;如果信号不是信号集的成员返回0;错误返回-1
eg:将2、3号信号添加到信号集中,并测试2号信号是否在信号集中
#include <stdio.h>
#include <signal.h>
int main(void)
{
//聲明信號集類型
sigset_t set;
//初始化信號集爲空
sigemptyset(&set);
//添加2、3號信號
sigaddset(&set, 2);
sigaddset(&set, 3);
//測試2號信號是否是信號集的成員
int n = sigismember(&set, 2);
if(n==-1){
printf("sigismember error...");
return -1;
}
n ? printf("set member...\n") : printf("is't set member...\n");
return 0;
}
- 进程的信号屏蔽字(信号掩码)
- 如何将一个信号集设置为信号的掩码呢?使用sigprocmask(2)函数
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//功能:设置进程的信号掩码
//参数
//how:有以下取值
//1).SIG_BLOCK:当前进程的信号掩码和set集合的并集,设置成进程的信号掩码
//2).SIG_UNBLOCK:将set集合里的信号从当前进程的信号掩码中移除
//3).SIG_SETMASK:信号掩码设置成set集合
//set:如果为空进程的信号集不改变,how参数将被忽略;如果不为空,信号掩码的当前值仍然在oldset中返回
//oldset:保存原来的信号掩码集,如果是NULL,不保存原来的
//返回值:成功返回0,;错误返回-1
eg:使用signalprocmask设置进程对2号信号阻塞
#include <stdio.h>
#include <signal.h>
int main(void)
{
sigset_t set;
//初始化信號集set爲空
sigemptyset(&set);
//將2號信號添加到set集合中
sigaddset(&set, 2);
//設置對2號信號阻塞
sigprocmask(SIG_SETMASK, &set, NULL);
while(1);//讓進程不要結束
return 0;
}
- 有些信号在阻塞的时候发送多次,进程在解除对信号的阻塞的时候,调用一个,这样的信号称为不可靠信号(1~31号信号)。信号丢失
- 在进程对信号阻塞的时候,信号发送多次,在进程解除对信号的阻塞以后,发送几次处理几次,这样的信号称为可靠信号(34~64号信号)。
eg:
#include <stdio.h>
#include <signal.h>
void doit(int n)
{
printf("recv...%d\n", n);
return;
}
int main(void)
{
int i=0;
sigset_t set, old_set;
signal(2, doit);
signal(45, doit);
//初始化信號集set爲空
sigemptyset(&set);
//將2,45號信號添加到set集合中
sigaddset(&set, 2);
sigaddset(&set, 45);
//設置對2號信號阻塞
sigprocmask(SIG_SETMASK, &set, &old_set);
for(i=0; i<8000000; i++){//確保長時間運行
printf("%d\n", i);
}
//恢復進程的屏蔽字
sigprocmask(SIG_SETMASK, &old_set, NULL);
return 0;
}
- 如何检测进程的未决信号?使用sigpending(2),信号要阻塞后才能捕获未决信号
#include <signal.h>
int sigpending(sigset_t *set);
//功能:检测未决信号
//参数
//set:指定存储未决信号集的地址
//返回值:成功返回0;错误返回-1
eg:
#include <stdio.h>
#include <signal.h>
int main(void)
{
sigset_t set, pset;
//初始化set集合
sigemptyset(&set);
//將2號信號加入set集合中
sigaddset(&set, 2);
//設置進程的信號掩碼爲set
sigprocmask(SIG_SETMASK, &set, NULL);
while(1){
sleep(2);
//獲取進程的未決信號
sigpending(&pset);
//檢測2號信號是不是未決信號的成員
int is = sigismember(&pset, 2);
if(is==-1){printf("sigismember error...\n"); return -1;}
is?printf("is member...\n"):printf("is't member...\n");
}
return 0;
}
- 阻塞(blocking)和未决(pending)信号都是进程控制块PCB中的一部分
三、信号从产生到处理的过程
- 1.在bash下启动进程a.out
- 2.按下ctrl+C键,产生硬件中断,不管进程运行在用户态还是内核态,进程都会切换到内核态
- 3.驱动程序会将中断解释为2号信号,将这个到达的信号记录到进程的PCB中
- 4.当进程从内核态切换回用户态的时候,检测PCB中是否有信号到达
- 5.如果没有信号到达,直接切回用户态,继续执行
- 6.如果有信号到达,调用信号的处理函数,信号处理函数执行完毕,调用sigreture,结束信号处理函数,清空函数的栈帧,返回到内核态。执行第4步。
四、可重入函数
- 只使用函数私有空间,不使用进程的全局空间,这样的函数称为可重入函数。(面试回答:如果函数中使用了全局变量、静态局部变量或malloc分配的内存,这样的函数就是不可重入函数。)
eg: 不可重入函数出现错误(当2号信号到达时也执行doit函数,导致打印了相同的val值)
#include <stdio.h>
#include <signal.h>
int val;
void doit(int n)
{
int c;
c=val;
c++;
printf("c=%d\n", c);
val=c;
return;
}
int main(void)
{
signal(2, doit);
while(1){
doit(100);
}
return 0;
}
五、前台作业和后台作业
ps -o pid,ppid,pgrp,session,comm
-
session是会话,会话跟终端相关,有的会话有终端,有的会话没有终端(精灵进程/守护进程)
-
进程组:父进程创建的子进程,子进程再创建子进程都是属于同一个进程组
-
作业:父进程和子进程是作业,而父进程和子进程的子进程不是同一个作业
-
有终端的会话有且仅有一个前台作业,可以有多个后台作业
-
使用
ctrl+z
将正在运行的作业放到后台停止 -
使用命令
jobs
查看后台作业 -
使用
fg %作业号
将后台作业切回到前台 -
使用
bf %作业号
放在后台运行 -
command&
执行作业的时候,直接放在后台
-
bash脚本:将要执行的Linux命令编写到一个文件中一起执行,这样的文件就是bash脚本文件。
eg:test.sh
#!/bin/bash
cat /etc/passwd
ls
- 使用快捷键只能给前台作业发送信号,不能给后台发送信号
================================================
信号到此结束!!!