网站开发销售简历范文,wordpress博客下载插件,wordpress媒体库是空的,网站建设 开发文章目录 一、Linux信号1. 信号的概念2. 信号的定义3. 系统定义的信号 二、信号产生的方式1.通过键盘产生2. 通过系统调用3. 软件条件4. 硬件异常 三、信号处理函数1. OS发送信号的实质2. 指令发送信号3. signal()4. sigaction() 四、信号屏蔽机制1. 信号处理方式2.信号集操作函… 文章目录 一、Linux信号1. 信号的概念2. 信号的定义3. 系统定义的信号 二、信号产生的方式1.通过键盘产生2. 通过系统调用3. 软件条件4. 硬件异常 三、信号处理函数1. OS发送信号的实质2. 指令发送信号3. signal()4. sigaction() 四、信号屏蔽机制1. 信号处理方式2.信号集操作函数 五、临界资源和临界区 Linux 信号是操作系统中的重要组成部分可以用于进程间通信、处理异常等多种场景。本文将深入介绍 Linux 信号的相关知识包括信号的定义、类型、发送和接收、处理等内容帮助读者更好地理解和使用 Linux 信号
一、Linux信号
1. 信号的概念
生活角度的信号
你在网上买了很多件商品再等待不同商品快递的到来。但即便快递没有到来你也知道快递来临时你该怎么处理快递。也就是你能“识别快递”当快递员到了你楼下你也收到快递到来的通知但是你正在打游戏需5min之后才能去取快递。那么在在这5min之内你并没有下去去取快递但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行可以理解成“在合适的时候去取”。在收到通知再到你拿到快递期间是有一个时间窗口的在这段时间你并没有拿到快递但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”当你时间合适顺利拿到快递之后就要开始处理快递了。而处理快递一般方式有三种 执行默认动作幸福的打开快递使用商品 执行自定义动作快递是零食你要送给你你的女朋友 忽略快递快递拿上来之后扔掉床头继续开一把游戏 快递到来的整个过程对你来讲是异步的你不能准确断定快递员什么时候给你打电话
技术应用角度的信号
用户输入命令,在Shell下启动一个前台进程。 用户按下Ctrl-C ,这个键盘输入产生一个硬件中断被OS获取解释成信号发送给目标前台进程前台进程因为收到信号进而引起进程退出 2. 信号的定义
信号是 Linux 操作系统中用于进程间通信、处理异常等情况的一种机制。它是由操作系统向一个进程或者线程发送的一种异步通知用于通知该进程或线程某种事件已经发生需要做出相应的处理。
信号的作用 进程间通信进程可以通过向其他进程发送信号的方式进行通信例如某个进程在完成了某项工作之后可以向另一个进程发送 SIGUSR1 信号通知其进行下一步的操作。 处理异常信号可以被用来处理程序中的异常情况例如当一个进程尝试访问未分配的内存或者除以 0 时系统会向该进程发送 SIGSEGV 或 SIGFPE 信号用于处理这些异常情况。 系统调试信号可以用于程序的调试例如在程序运行时可以向该进程发送 SIGUSR2 信号用于打印程序的状态信息等。
3. 系统定义的信号
Linux 中信号分为标准信号和实时信号每个信号都有一个唯一的编号。
标准信号最基本的信号类型由整数编号表示编号范围是 1 到 31。实时信号Linux 中的扩展信号类型由整数编号表示编号范围是 32 到 64。
可以用kill -l命令可以察看系统定义的信号列表
kill -l查询结果如下:
常见信号编号以及对应信号的名称
注意不同的操作系统可能对信号的编号有所不同因此在跨平台开发时应当注意信号编号的兼容性。
term 表示终止进程core 表示生成核心转储文件核心转储文件可用于调试ignore 表示忽略信号cont 表示继续运行进程stop 表示停止进程注意停止不等于终止而是暂停
相关信号的解读
2) SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出用于通知前台进程组终止进程。3) SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-/)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。4) SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。5) SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用。6) SIGABRT
调用abort函数生成的信号。7) SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。8) SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了可尝试发送这个信号。10) SIGUSR1
留给用户使用11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.12) SIGUSR2
留给用户使用13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生比如采用FIFO(管道)通信的两个进程读管道没打开或者意外终止就往管道写写进程会收到SIGPIPE信号。此外用Socket通信的两个进程写进程在写Socket的时候读进程已经终止。14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.15) SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出shell命令kill缺省产生这个信号。如果进程终止不了我们才会尝试SIGKILL。17) SIGCHLD
子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号也没有等待(wait)子进程子进程虽然终止但是还会在内核进程表中占有表项这时的子进程称为僵尸进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号或者捕捉它或者wait它派生的子进程或者父进程先终止这时子进程的终止自动由init进程 来接管)。18) SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符19) SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号21) SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.23) SIGURG
有紧急数据或out-of-band数据到达socket时产生.24) SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.27) SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.28) SIGWINCH
窗口大小改变时发出.29) SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.30) SIGPWR
Power failure31) SIGSYS
非法的系统调用。在以上列出的信号中程序不可捕获、阻塞或忽略的信号有SIGKILL,SIGSTOP不能恢复至默认动作的信号有SIGILL,SIGTRAP默认会导致进程流产的信号有SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ默认会导致进程退出的信号有 SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM默认会导致进程停止的信号有SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU默认进程忽略的信号有SIGCHLD,SIGPWR,SIGURG,SIGWINCH此外SIGIO在SVR4是退出在4.3BSD中是忽略SIGCONT在进程挂起时是继续否则是忽略不能被阻塞。
二、信号产生的方式
1.通过键盘产生
Ctrl C 通过键盘敲入 Ctrl C 可以向进程发送 2 号信号使其终止。
2. 通过系统调用
① kill
#includesignal.h
#includesys/types.hint kill(int pid,int signal); //通过函数向指定进程发送信号② raise
raise接口可以向当前调用进程发送任意信号
可以给当前进程发送指定的信号即自己给自己发信号
#include stdio.h
#include signal.hraise(11); //向当前进程发送 11 号信号③ abort
使当前进程接收到信号而异常终止
#include stdio.h
#include stdlib.habort();3. 软件条件
alarm
调用alarm函数可以设定一个闹钟也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号该信号的默认处理动作是终止当前进程
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数
打个比方某人要小睡一觉设定闹钟为30分钟之后响20分钟后被人吵醒了还想多睡一会儿于是重新设定闹钟为15分钟之后响“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0表示取消以前设定的闹钟函数的返回值仍然是以前设定的闹钟时间还余下的秒数
#include stdio.h
#include unistd.hint main()
{int ret alarm(20);while (1) {printf(I am a process, ret %d\n, ret);sleep(5);int res alarm(0); //取消闹钟printf(res %d\n, res);}return 0;
}4. 硬件异常
#include stdio.h
#include signal.hvoid handler(int sig){printf(catch a sig : %d\n, sig);}int main(){signal(SIGSEGV, handler);sleep(1);int *p NULL;*p 100;while(1);return 0;}这里出现野指针异常。系统本该直接清除掉该进程但因为修改了野指针异常所对应的操作函数导致系统只打印了一句话而不正常的对该进程进行清除。但系统不断地检测到有野指针异常。但操作系统做的工作仅仅是打印一句话。那么这里就造成了循环打印catch a sig : xxx 三、信号处理函数
1. OS发送信号的实质
信号产生的方式种类虽然非常多但是无论产生信号的方式千差万别但是最终一定都是通过OS向目标进程发送信号。产生信号的方式其实都是OS发送信号数据给task_struct
struct task_struct 中有进程的各种属性那么其中也一定有对应的数据变量来保存是否收到了对应的信号而信号的编号也是有规律的1~31
进程中采用 uint32_t sigs ;——位图结构来标识该进程是否收到信号
0000 0000 0000 0000 0000 0000 0000 0000
比特位的位置第几个代表的就是哪一个信号比特位的内容0或1代表的就是是否收到了信号
故本质是OS向指定进程的task_struct中的信号位图写入比特1即完成信号的发送也可以说是信号的写入
struct task_struct
{//信号位图0000 0000 10...uint32_t sigmap;
}2. 指令发送信号 kill 命令 kill 命令是 Linux 中最常用的发送信号的命令语法如下 kill [-signal] PID其中-signal 可选参数表示要发送的信号类型如果省略该参数则默认发送 SIGTERM 信号。PID 表示接收信号的进程 ID。 例如要向进程 ID 101080 发送 SIGINT 信号可以执行以下命令 kill -SIGINT 101080
kill -2 101080当然可以发送对应的信号编号 kill 函数 除了使用 kill 命令程序中也可以通过 kill 函数来发送信号。kill 函数的原型如下 int kill(pid_t pid, int sig);其中pid 表示接收信号的进程 IDsig 表示要发送的信号类型。如果函数调用成功则返回 0否则返回 -1 并设置 errno。 例如要向进程 ID 123 发送 SIGINT 信号可以执行以下代码 #include signal.h
#include unistd.hint main()
{pid_t pid 123;int sig SIGINT;if (kill(pid, sig) -1) {perror(kill);return 1;}return 0;
}3. signal()
signal 函数可以用来自定义信号捕捉函数
#include signal.h
typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);//示例
void handler(int signum)
{coutget a signum: signumendl;
}int main()
{signal(14,handler); //此时进程收到14号信号的默认处理动作就是执行handler函数
}signal 函数的两个宏
signal(2,SIG_DFL); //default 使对应的2号信号恢复默认动作
signal(2,SIG_IGN); //ignore 忽略二号信号参数: signum此参数指定需要进行设置的信号可使用信号名宏或信号的数字编号建议使用信号名。handlersighandler_t 类型的函数指针指向信号对应的信号处理函数当进程接收到信号后会自动执行该处理函数参数 handler 既可以设置为用户自定义的函数也就是捕获信号时需要执行的处理函数也可以设 置为 SIG_IGN 或 SIG_DFLSIG_IGN 表示此进程需要忽略该信号SIG_DFL 则表示设置为系统默认操作。sighandler_t 函数指针的 int 类型参数指的是当前触发该函数的信号可将多个信号绑定到同一个信号处理函数 上此时就可通过此参数来判断当前触发的是哪个信号。 返回值此函数的返回值也是一个 sig_t 类型的函数指针 成功返回值则是指向在此之前的信号处理函数出错则返回 SIG_ERR并会设置 errno。
由此可知signal()函数可以根据第二个参数 handler 的不同设置情况可对信号进行不同的处理。
4. sigaction()
在Linux中sigaction函数是用于设置和检索信号处理器的函数。
sigaction函数有以下语法
#include signal.hint sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);其中signum 表示要注册的信号编号act 是一个指向 struct sigaction 结构体的指针表示新的信号处理函数和信号处理选项oldact 是一个指向 struct sigaction 结构体的指针用于获取之前注册的信号处理函数和信号处理选项。
struct sigaction 结构体的定义如下
struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
};sa_handler指定信号处理函数的地址。如果设置为SIG_IGN则表示忽略该信号。如果设置为SIG_DFL则表示使用默认处理器也可以自己设置需处理的函数逻辑。 sa_sigaction指定一个信号处理器函数这个函数包含三个参数一个整数表示信号编号一个指向siginfo_t结构体的指针和一个指向void类型的指针。 sa_mask指定了在执行信号处理函数期间要阻塞哪些信号。 sa_flags是一个标志位可以包括以下值 SA_NOCLDSTOP如果设置了该标志则当子进程停止或恢复时不会生成SIGCHLD信号。SA_RESTART如果设置了该标志则系统调用在接收到信号后将被自动重启。SA_SIGINFO如果设置了该标志则使用sa_sigaction字段中指定的信号处理器。 sa_restorer是一个指向恢复函数的指针用于恢复某些机器状态。
返回值如果成功则返回0否则返回-1并设置errno错误号。可以使用以下代码来检查errno
注意一般情况下sa_handler 和 sa_mask 使用较多这里再次详细说明 sa_handler 赋值为常数SIG_IGN表示忽略信号 赋值为常数SIG_DFL表示执行系统默认动作 赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数 该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信 号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。 sa_mask 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来 的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。如果 在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号, 则用sa_mask字段说明这些需 要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 注意信号在被处理前pendding位图就修改了对应的比特位
四、信号屏蔽机制
1. 信号处理方式 信号的处理方式有三种称为信号的递达 信号的忽略( SIG_IGN )即不采取任何动作信号的默认使用系统默认的处理方法信号的自定义捕捉采用用户定义的方法处理 信号从产生到递达之间的状态称为信号的未决( Pending ) 信号在位图中未被处理(未决) 进程选择阻塞( Block )某个信号 未决之后暂时不递达直到解除对信号的阻塞期间对该信号为屏蔽
信号在内核中的表示示意图 block 信号阻塞表 在该表中标记为 1 的信号将被阻塞 pending 信号未决表 信号被进程接收但还未被处理的信号就存放在该表中 普通信号只在pending表记录一次如果产生多次也只记录一次之后系统对该信号的处理动作为 1 次实施信号采用队列的形式记录历史中产生的所有信号后系统对该信号的处理动作为 1历史中产生的次数 注意pendding 表存在多个信号时会全部处理完才返回用户端 bandler 信号对应处理方法表 该表中记录了信号对应操作方法的函数指针。
2.信号集操作函数
头文件
#include signal.h //头文件sigemptyset int sigemptyset(sigset_t *set);初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。 sigemptyset int sigemptyset(sigset_t *set);函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。 sigfillset int sigfillset(sigset_t *set);函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。 sigaddset int sigaddset (sigset_t *set, int signo); 添加屏蔽信号 sigdelset int sigdelset(sigset_t *set, int signo);删除屏蔽信号 sigismember int sigismember(const sigset_t *set, int signo); //查询是否有该屏蔽信号示例:
sigset_t set oset;
sigismember(pending , int signo)查询是否有该屏蔽信号 sigprocmask int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 示例: sigset_t set, oset;
sigprocmask(SIG_BLOCK, set, oset);参数: how SIG_BLOCK //添加屏蔽字
SIG_UNBLOCK //删除
SIG_SETMASK //直接用新的替换const sigset_t 当前需要修改的 sigset_t oset 传出之前的pending码 使用示例: int main()
{
sigset_t set, oset;
// 初始化,全置空
// sigfillset 全置1
sigemptyset(set);
sigemptyset(oset);// 设置屏蔽字
// sigdelset 清除
sigaddset(set, 2); // 对于set位图对2号信号设置屏蔽字// 将 set 信号设置进入当前进程sigprocmask(SIG_BLOCK, set, oset); // oset为之前的屏蔽字
}sigpending int sigpending(sigset_t *set);示例:
sigset_t pending;
sigemptyset(pending);功能读取当前进程的未决信号集,通过set参数传出。调用成功则返回 0 ,出错则返回 -1 示例: //通过此中方式读出void Printsignal (const sigset_t pending)
{for(int signo 31; signo 0; signo--){if(sigismember(pending, signo)){std::cout 1;}else{std::cout 0;}}std::cout \n;}五、临界资源和临界区 临界资源 概念被保护起来的公共资源一次仅允许一个进程使用的共享资源。其他都是非临界资源 临界区 概念每个进程中访问临界资源的那段程序(代码)称之为临界区。 临界区不是内核对象而是系统提供的一种数据结构程序中可以声明一个该类型的变量之后用它来实现对资源的互斥访问。 当欲访问某一临界资源时先将该临界区加锁若临界区不空闲则等待用完该资源后将临界区释放。 补充待定分类临界区也是代码的称呼所以一个进程可能有多个临界区分别用来访问不同的临界资源。 内核程序临界资源系统时钟 普通临界资源普通I/O设备如打印机进程访问这些资源的时候很慢会自动阻塞等待资源使用完成 信号量 表示资源数目的计数器。每一个执行流想访问公共资源内的某一份资源不应该让执行流直接访问而是先申请信号量。资源其实就是对信号量计数器进行自减操作本质上只要自减成功。就完成了对资源的预定机制。如果申请不成功执行流将被挂起阻塞。 进程进入临界区的调度原则 如果有若干进程请求进入空闲的临界区空闲即0进程访问一次仅允许 一个进程进入。任何时候处于临界区内的进程不可多于一个0 或 1若已有进程进入自己的临界区则其它想进入自己临界区的进程必须等待。进行临界区的进程要在有限时间内退出以便其它进程能及时进入自己的临界区。如果其它进程不能进入自己的临界区则应让出 CPU避免进程出现 “忙等” 现象。访问临界资源时先访问信号量资源可以理解为信号量引用计数自减1如果自减成功说明还能访问未达到上限允许访问访问结束后信号量自增1临界资源的访问是原子的 细节问题 每个进程都得先看到同一个信号量资源就只能由OS提供IPC体系。 信号量本身也是公共资源。 单个信号量 struct sem
{int count; //引用计数task_struct *wait_queue; //进程等待队列
}//如果当前引用计数不为零那么新的进程将会直接被运行。
//如果当前引用计数为零那么新的进程将会加入等待队列。当别的进程结束时引用计数自增同时等待队列中的进程将会被执行,同时引用计数自减