10.Linux进程信号

news2025/6/8 21:55:17

1. 理解信号

信号VS信号量 = 老婆:老婆饼-》没有任何关系!

信号:闹钟,上课铃声,脸色...人-》进程;信号中断人正在做的事,是一种事件的异步通知机制;
我们自习一会,等张三回来再讲 -- 同步
我们继续上课,而张三去取快递 -- 异步

信号->是一种给进程发送的,用来进行事件异步通知的机制!信号的产生,相对于进程的运行,是异步的!信号是发给进程的

基本结论:

1.你怎么能识别信号呢?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。
2.进程早已经内置了对于信号的识别和处理方式
3.处理信号,⽴即处理吗?我可能正在做优先级更⾼的事情,不会⽴即处理?什么时候?合
适的时候。
4.信号到来 | 信号保存 | 信号处理
5.怎么进⾏信号处理啊?a.默认 b.忽略 c.⾃定义, 后续都叫做信号捕捉。

 

1.1 一个例子

⽤⼾输⼊命令,在Shell下启动⼀个前台进程
• ⽤⼾按下 Ctrl+C ,这个键盘输⼊产⽣⼀个硬件中断,被OS获取,解释成信号,发送给⽬标前台进
程
• 前台进程因为收到信号,进⽽引起进程退出

 

注意:

1. 要注意的是,signal函数仅仅是设置了特定信号的捕捉⾏为处理⽅式,并不是直接调⽤处
理动作。如果后续特定信号没有产⽣,设置的捕捉函数永远也不会被调⽤!!

2. Ctrl-C 产⽣的信号只能发给前台进程。⼀个命令后⾯加个&可以放到后台运⾏,这样
Shell不必等待进程结束就可以接受新的命令,启动新的进程。0

3. Shell可以同时运⾏⼀个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C
这种控制键产⽣的信号。

4. 前台进程在运⾏过程中⽤⼾随时可能按下 Ctrl-C ⽽产⽣⼀个信号,也就是说该进程的⽤
⼾空间代码执⾏到任何地⽅都有可能收到 SIGINT 信号⽽终⽌,所以信号相对于进程的控
制流程来说是异步(Asynchronous)的。

 1.2 信号处理

1. 自定义 -》 signal(SIGINT/*2*/, 函数指针);

2. 忽略此信号 -》 signal(SIGINT/*2*/, SIG_IGN); // 设置忽略信号的宏

3. 执⾏该信号的默认处理动作 -》 signal(SIGINT/*2*/, SIG_DFL);// 默认信号处理


源码:

#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */

/* Type of a signal handler. */
typedef void (*__sighandler_t) (int);

其实SIG_DFL和SIG_IGN就是把0,1强转为函数指针类型

2. 产生信号

2.1 键盘产生信号

• Ctrl+C (SIGINT) 已经验证过,这⾥不再重复
• Ctrl+\(SIGQUIT)可以发送终⽌信号并⽣成core dump⽂件,⽤于事后调试
• Ctrl+Z(SIGTSTP)可以发送停⽌信号,将当前前台进程挂起到后台等

相当一部分信号的处理动作,就是让自己终止。

进程收到信号后,合适的时候,处理信号动作有三种:1.默认处理动作 2.自定义信号处理动作(自定义捕捉)3.忽略处理

 编号34以上的是实时信号;这些信号各⾃在什么条件下产⽣,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal。

 2.1.1 OS如何得知键盘有数据

1.键盘按下
2.向CPU发送硬件中断
3.CPU识别自身针脚具有硬件中断信息,其实就是高电压
4.CPU执行OS中处理键盘数据的代码
5.OS停下来当前工作,将数据从外设读入内存,等待进一步处理

2.1.2 初步理解信号起源 

• 信号其实是从纯软件⻆度,模拟硬件中断的⾏为
• 只不过硬件中断是发给CPU,⽽信号是发给进程
• 两者有相似性,但是层级不同

 2.1.3 理解前后台进程

目标进程:前台进程和后台进程
# ./XXX --- 前台进程     --键盘产生的信号,只能发给前台进程
# ./XXX & --- 后台进程
命令行shell进程 --- 前台


后台进程,无法从标准输入(键盘)中获取内容;而前台可以
但是都可以向标准输出上打印

前台进程必须只有一个,后台进程可以有多个

XXX
fork()
父 -- 先退出了
子 -- 孤儿进程 --》自动提到后台 -》ctrl + c 杀不掉
补充命令:前后台移动
jobs查看所有的后台任务
fg 任务号,特定的进程,提到前台

ctrl + z:进程切换到后台
bg 任务号:让后台进行回复运行

 2.1.4 什么叫做给进程发送信号

信号产生后,并不是立即处理的,所以要求,进程必须把信号记录下来
记录在:
struct task_struct
{
    unsigned int sigs;//位图结构
}//比特位位置,信号编号;
 //比特位内容,是否收到
这属于OS内的数据结构对象,修改位图的本质就是修改内核的数据
不管信号怎么产生,发送信号,在底层,必须让给OS发送
修改位图是OS自己完成的,它会提供发送信号的系统调用kill

发送信号的本质是:向目标进程写信号,修改位图,pid,信号的编号

2.2 系统调用

⾸先在后台执⾏死循环程序,然后⽤kill命令给它发SIGSEGV信号

$ kill -SIGSEGV 213784
$ // 多按⼀次回⻋
[1]+ Segmentation fault ./sig

• 213784 是 sig 进程的pid。之所以要再次回⻋才显⽰ Segmentation fault ,是因为在
213784 进程终⽌掉之前已经回到了Shell提⽰符等待⽤⼾输⼊下⼀条命令, Shell 不希望
Segmentation fault 信息和⽤⼾的输⼊交错在⼀起,所以等⽤⼾输⼊命令之后才显⽰。

• 指定发送某种信号的 kill 命令可以有多种写法,上⾯的命令还可以写成 kill -11
213784 , 11 是信号 SIGSEGV 的编号。以往遇到的段错误都是由⾮法内存访问产⽣的,⽽这个
程序本⾝没错,给它发SIGSEGV也能产⽣段错误。

2.3 使用函数产生信号

2.3.1 kill

kill第二个参数:信号编号(Signal Number)

 实现⾃⼰的 kill 命令

 2.3.2 raise

raise(sig) 等价于执行 kill(getpid(), sig)

应用场景:
1.异常处理与程序终止
在检测到严重错误时,主动触发 SIGABRT(异常终止)或 SIGTERM(正常终止)
。。。
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.

 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 [硬件]异常(系统崩掉)

硬件异常被硬件以某种⽅式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执⾏了除以0的指令, CPU的运算单元会产⽣异常, 内核将这个异常解释为SIGFPE信号发送给进程。再⽐如当前进程访问了⾮法内存地址(野指针), MMU会产⽣异常,内核将这个异常解释SIGSEGV信号发送给进程。
我们在C/C++当中除零,内存越界等异常,在系统层⾯上,是被当成信号处理的

除0后发现⼀直有8号信号产⽣被我们捕获,这是为什么呢?上⾯我们只提到CPU运算异常后,如何
处理后续的流程,实际上 OS 会检查应⽤程序的异常情况,其实在CPU中有⼀些控制和状态
寄存器,主要⽤于控制处理器的操作,通常由操作系统代码使⽤。状态寄存器可以简单理解
为⼀个位图,对应着⼀些状态标记位、溢出标记位。OS 会检测是否存在异常状态,有异常存
在就会调⽤对应的异常处理⽅法。
除零异常后,我们并没有清理内存,关闭进程打开的⽂件,切换进程等操作,所以CPU中还
保留上下⽂数据以及寄存器内容,除零异常会⼀直存在,就有了我们看到的⼀直发出异常信
号的现象。

2.5 软件条件

• 调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发
SIGALRM 信号,该信号的默认处理动作是终⽌当前进程。
• 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个⽐⽅,某⼈要⼩睡⼀觉,设定闹
钟为30分钟之后响,20分钟后被⼈吵醒了,还想多睡⼀会⼉,于是重新设定闹钟为15分钟之后响,“以
前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表⽰取消以前设定的闹钟,函数
的返回值仍然是以前设定的闹钟时间还余下的秒数。

2.5.1 利用alarm -- 体会IO效率问题

IO多时(cout等等),效率明显下降;与IO少的相比,是数量级的差别(十万:1亿)

 2.5.2 设置重复闹钟

pause() 是一个简单但强大的系统调用,用于让进程等待信号。它在实现事件驱动的程序、
超时控制或资源等待时非常有用。使用时需注意信号处理函数的设计和竞态条件,
必要时结合 sigprocmask() 和 sigsuspend() 确保安全。

NAME
    pause - wait for signal

SYNOPSIS
    #include <unistd.h>
    
    int pause(void);

DESCRIPTION
    pause() causes the calling process (or thread) to sleep until a signal
    is delivered that either terminates the process or causes the invoca‐
    tion of a signal-catching function.

RETURN VALUE
    pause() returns only when a signal was caught and the signal-catching
    function returned. In this case, pause() returns -1, and errno is set
    to EINTR

 实例:

2.5.3 如何理解软件条件

在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产⽣机制。这些条
件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据
产⽣的SIGPIPE信号)等。当这些软件条件满⾜时,操作系统会向相关进程发送相应的信号,以通知
进程进⾏相应的处理。简⽽⾔之,软件条件是因操作系统内部或外部软件操作⽽触发的信号产⽣。

 

3. 保存信号

• 实际执⾏信号的处理动作称为信号递达(Delivery) -> 自定义,默认,忽略
• 信号从产⽣到递达之间的状态,称为信号未决(Pending) -> 信号在位图中,还没来得及处理
• 进程可以选择阻塞 (Block )某个信号。
• 被阻塞的信号产⽣时将保持在未决状态,直到进程解除对此信号的阻塞,才执⾏递达的动作.
• 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,⽽忽略是在递达之后可选的⼀种处理动
作。

3.1 在内核中的表示

1. 每个信号都有两个标志位分别表⽰阻塞(block)和未决(pending),还有⼀个函数指针表⽰处理动
作。信号产⽣时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上
图的例⼦中,SIGHUP信号未阻塞也未产⽣过,当它递达时执⾏默认处理动作。

2. SIGINT信号产⽣过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻
塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

3. SIGQUIT信号未产⽣过,⼀旦产⽣SIGQUIT信号将被阻塞,它的处理动作是⽤⼾⾃定义函数
sighandler。
 如果在进程解除对某信号的阻塞之前这种信号产⽣过多次,将如何处理?POSIX.1允许系统递送该信
号⼀次或多次。Linux是这样实现的:常规信号在递达之前产⽣多次只计⼀次,⽽实时信号在递达之
前产⽣多次可以依次放在⼀个队列⾥。

3.2 sigset_t

,每个信号只有⼀个bit的未决标志, ⾮0即1, 不记录该信号产⽣了多少次,阻塞标志也是这样
表⽰的。因此, 未决和阻塞标志可以⽤相同的数据类型sigset_t来存储, , 这个类型
可以表⽰每个信号的“有效”或“⽆效”状态, 在阻塞信号集中“有效”和“⽆效”的含义是该信号
是否被阻塞, ⽽在未决信号集中“有 效”和“⽆效”的含义是该信号是否处于未决状态。

阻塞信号集也叫做当前进程的 这⾥的“屏蔽”应该理解为阻塞⽽不是忽略。

3.3 信号集操作函数

sigset_t类型对于每种信号⽤⼀个bit表⽰“有效”或“⽆效”状态, ⾄于这个类型内部如何存储这些
bit则依赖于系统实现, 从使⽤者的⻆度是不必关⼼的, 使⽤者只能调⽤以下函数来操作sigset_ t变量,
⽽不应该对它的内部数据做任何解释, ⽐如⽤printf直接打印sigset_t变量是没有意义的。


#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

• 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表⽰该信号集不包含
任何有效信号。
• 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表⽰ 该信号集的有效信号
包括系 统⽀持的所有信号。
• 注意,在使⽤sigset_ t类型的变量之前,⼀定要调 ⽤sigemptyset或sigfillset做初始化,
使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调⽤sigaddset和
sigdelset在该信号集中添加或删除某种有效信号。

这四个函数都是成功返回0,出错返回-1。sigismember是⼀个布尔函数,⽤于判断⼀个信号集的有效信
号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1

3.3.1  sigprocmask

调⽤函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

如果oset是⾮空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是⾮空指针,则 更改
进程的信 号屏蔽字,参数how指⽰如何更改。如果oset和set都是⾮空指针,则先将原来的信号 屏蔽字
备份到oset⾥,然后 根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了
how参数的可选值。

如果调⽤sigprocmask解除了对当前若⼲个未决信号的阻塞,则在sigprocmask返回前,⾄少将其中⼀
个信号递达。

3.3.2 sigpending

#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。
调⽤成功则返回0,出错则返回-1

 3.3.3 Core vs Term

Core:
核心:会在当前路径下,形成一个文件,进程异常退出的时候,进程在内存中的核心数据,
从内存拷贝到磁盘,形成一个文件(核心转储 -- 支持debug)然后进程退出

Term:进程直接退出

为什么会核心转储???
支持debug -- 开启core dump,直接运行崩溃,gdb,core-file core,直接帮助我们定位到
出错行 --- 事后调试!

4. 捕捉信号

信号的处理,不是立即处理,而是可以等一会再处理,合适的时候,进行信号处理

4.1 信号捕捉的流程

如果信号的处理动作是⽤⼾⾃定义函数,在信号递达时就调⽤这个函数,这称为捕捉信号。
由于信号处理函数的代码是在⽤⼾空间的,处理过程⽐较复杂,举例如下:

• ⽤⼾程序注册了 SIGQUIT 信号的处理函数 sighandler 。
• 当前正在执⾏ main 函数,这时发⽣中断或异常切换到内核态。
• 在中断处理完毕后要返回⽤⼾态的 main 函数之前检查到有信号 SIGQUIT 递达(信号检查)
• 内核决定返回⽤⼾态后不是恢复 main 函数的上下⽂继续执⾏,⽽是执⾏ sighandler 函
数, sighandler 和 main 函数使⽤不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是两个
独⽴的控制流程(自定义方法必须以用户身份执行)
• sighandler 函数返回后⾃动执⾏特殊的系统调⽤ sigreturn 再次进⼊内核态。
• 如果没有新的信号要递达,这次再返回⽤⼾态就是恢复 main 函数的上下⽂继续执⾏了。

4.2 sigaction

可以设置信号屏蔽字,可以同时处理多个不同信号,但是不能同时处理多个相同信号

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

1. sigaction函数可以读取和修改与指定信号相关联的处理动作。调⽤成功则返回0,出错则返回- 1。
signo是指定信号的编号。若act指针⾮空,则根据act修改该信号的处理动作。若oact指针⾮空, 则
通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:

2. 将sa_handler赋值为常数SIG_IGN传给sigaction表⽰忽略信号,赋值为常数SIG_DFL表⽰执⾏系统
默认动作,赋值为⼀个函数指针表⽰⽤⾃定义函数捕捉信号,或者说向内核注册了⼀个信号处理函
数,该函数返回值为void,可以带⼀个int参数,通过参数可以得知当前信号的编号,这样就可以⽤同⼀
个函数处理多种信号。显然,这也是⼀个回调函数,不是被main函数调⽤,⽽是被系统所调⽤。

3. 当某个信号的处理函数被调⽤时,内核⾃动将当前信号加⼊进程的信号屏蔽字,当信号处理函数返回时⾃
动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产⽣,那么 它会被阻塞到
当前处理结束为⽌。 如果在调⽤信号处理函数时,除了当前信号被⾃动屏蔽之外,还希望⾃动屏蔽另外⼀
些信号,则⽤sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时⾃动恢复原来的信号屏蔽字。

4.3 操作系统是怎么运行的

4.3.1 硬件中断

• 中断向量表就是操作系统的⼀部分,启动就加载到内存中了
• 通过外部硬件中断,操作系统就不需要对外设进⾏任何周期性的检测或者轮询
• 由外部设备触发的,中断系统运⾏流程,叫做硬件中断

4.3.2 时钟中断

当没有中断到来的时候,OS在做什么??什么也没做!!OS是暂停的;for(;;) pause(); 时钟源会以固定频率,源源不断向CPU发送时钟中断,就这样,OS就在硬件时钟中断的驱动下,进行调度了!所以,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.3.3 死循环

如果是这样,操作系统不就可以躺平了吗?对,操作系统⾃⼰不做任何事情,需要什么功能,就向中断向量表⾥⾯添加⽅法即可.操作系统的本质:就是⼀个死循环!

4.3.4 软中断

为了让操作系统⽀持进⾏系统调⽤,CPU也设计了对应的汇编指令(int 或者 syscall),可以让CPU内 部触发中断逻辑;软中断由软件主动发起(如系统调用、异常处理、内核线程调度),无需硬件信号触发。

系统调用通过中断机制(如 x86 的int 0x80syscall指令)实现用户态到内核态的切换,确保内核不受用户程序非法操作的影响(汇编和C可以混用)

当我们进行系统调用的时候,具体是怎么进入OS完成调用过程的,毕竟CPU只有一个?

通过系统调用表 -> 从此以后,每一个系统调用,都有一个唯一的下标!!这个下标,我们叫做,系统调用号!!!

像open,fork这样的函数本身并不是OS提供的!!

OS不提供任何系统调用接口!!OS只提供 系统调用号!!

系统调用的过程,也是在进程地址空间上进行的!!所有的函数调用,都是地址空间之间的跳转!

• ⽤⼾层怎么把系统调⽤号给操作系统? - 寄存器(⽐如EAX)
• 操作系统怎么把返回值给⽤⼾?- 寄存器或者⽤⼾传⼊的缓冲区地址
• 系统调⽤的过程,其实就是先int 0x80、syscall陷⼊内核,本质就是触发软中断,CPU就会⾃动执
⾏系统调⽤的处理⽅法,⽽这个⽅法会根据系统调⽤号,⾃动查表,执⾏对应的⽅法
• 系统调⽤号的本质:数组下标!
可是为什么我们⽤的系统调⽤,从来没有⻅过什么 int 0x80 或者 syscall 呢?都是直接调⽤
上层的函数的啊?
那是因为Linux的gnu C标准库,给我们把⼏乎所有的系统调⽤全部封装了

• ⽤⼾态就是执⾏⽤⼾[0,3]GB时所处的状态
• 内核态就是执⾏内核[3,4]GB时所处的状态
• 区分就是按照CPU内的CPL决定,CPL的全称是Current Privilege Level,即当前特权级别。
• ⼀般执⾏ int 0x80 或者 syscall 软中断,CPL会在校验之后⾃动变更

4.3.5 缺页中断?内存碎片处理?除0野指针错误?

缺⻚中断?内存碎⽚处理?除零野指针错误?这些问题,全部都会被转换成为CPU内部的软中断,
然后⾛中断处理例程,完成所有处理。有的是进⾏申请内存,填充⻚表,进⾏映射的。有的是⽤来
处理内存碎⽚的,有的是⽤来给⽬标进⾏发送信号,杀掉进程等等。
• 操作系统就是躺在中断处理例程上的代码块!
• CPU内部的软中断,⽐如int 0x80或者syscall,我们叫做 陷阱
• CPU内部的软中断,⽐如除零/野指针等,我们叫做 异常
缺页中断:
缺页中断是操作系统存储管理(尤其是虚拟存储系统)中的重要概念,属于中断机制的一种。
当进程访问的逻辑地址(虚拟地址)对应的页面不在物理内存中时,操作系统会触发缺页中断,
将缺失的页面从外存(如磁盘)调入内存后,再让进程继续执行。

4.4 如何理解内核态和用户态

用户和内核,都在同一个[0,4GB]的地址空间上了;如果用户随便拿一个虚拟地址[3,4GB],用户不就可以随便访问内核中的代码和数据了吗?

OS为了保护自己,不相信任何人!!只能采用系统调用的方式进行访问!

用户态:以用户的身份,只能访问自己的[0,3GB];

内核态:以内核的身份,运行你通过系统调用的方式,访问OS[3,4GB];

5. 可重入函数

1. main函数调⽤insert函数向⼀个链表head中插⼊节点node1,插⼊操作分为两步,刚做完第⼀步的
时候,因为硬件中断使进程切换到内核,再次回⽤⼾态之前检查到有信号待处理,于是切换 到
sighandler函数,sighandler也调⽤insert函数向同⼀个链表head中插⼊节点node2,插⼊操作的
两步都做完之后从sighandler返回内核态,再次回到⽤⼾态就从main函数调⽤的insert函数中继续
往下执⾏,先前做第⼀步之后被打断,现在继续做完第⼆步。结果是,main函数和sighandler先后 向
链表中插⼊两个节点,⽽最后只有⼀个节点真正插⼊链表中了。

2. 像上例这样,insert函数被不同的控制流程调⽤,有可能在第⼀次调⽤还没返回时就再次进⼊该函
数,这称为重⼊,insert函数访问⼀个全局链表,有可能因为重⼊⽽造成错乱,像这样的函数称为 不可
重⼊函数,反之,如果⼀个函数只访问⾃⼰的局部变量或参数,则称为可重⼊(Reentrant) 函数。

3. 如果⼀个函数符合以下条件之⼀则是不可重⼊的:
• 调⽤了malloc或free,因为malloc也是⽤全局链表来管理堆的。
• 调⽤了标准I/O库函数。标准I/O库的很多实现都以不可重⼊的⽅式使⽤全局数据结构。

6. volatile

标准情况下,键⼊ CTRL-C ,2号信号被捕捉,执⾏⾃定义动作,修改 flag=1 , while 条件不
满⾜, 退出循环,进程退出

优化情况下,键⼊ CTRL-C ,2号信号被捕捉,执⾏⾃定义动作,修改 flag=1 ,但是 while 条
件依旧满⾜,进程继续运⾏!但是很明显flag肯定已经被修改了,但是为何循环依旧执⾏?很明显,
while 循环检查的 flag,并不是内存中最新的 flag,这就存在了数据⼆异性的问题。 while 检
测的 flag 其实已经因为优化,被放在了CPU寄存器当中。如何解决呢?很明显需要 volatile

volatile 作⽤:保持内存的可⻅性,告知编译器,被该关键字修饰的变量,不允许被优化,对该
变量的任何操作,都必须在真实的内存中进⾏操作

7. SIGCHLD信号(了解)

⽤wait和waitpid函数清理僵⼫进程,⽗进程可以阻塞等待⼦进程结束,也可以⾮阻 塞地查询是否有⼦进程结束等待清理(也就是轮询的⽅式)。采⽤第⼀种⽅式,⽗进程阻塞了就不 能处理⾃⼰的⼯作了;采⽤第⼆种⽅式,⽗进程在处理⾃⼰的⼯作的同时还要记得时不时地轮询⼀ 下,程序实现复杂。其实,⼦进程在终⽌时会给⽗进程发SIGCHLD信号,该信号的默认处理动作是忽略,⽗进程可以⾃ 定义 SIGCHLD信号的处理函数,这样⽗进程只需专⼼处理⾃⼰的⼯作,不必关⼼⼦进程了,⼦进程 终⽌时会通知⽗进程,⽗进程在信号处理函数中调⽤wait清理⼦进程即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2404610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习基础(四) 决策树

决策树简介 决策树结构&#xff1a; 决策树是一种树形结构&#xff0c;树中每个内部节点表示一个特征上的判断&#xff0c;每个分支代表一个判断结果的输出&#xff0c;每个叶子节点代表一种分类结果 决策树构建过程&#xff08;三要素&#xff09;&#xff1a; 特征选择 选…

CentOS 7如何编译安装升级gcc至7.5版本?

CentOS 7如何编译安装升级gcc版本? 由于配置CentOS-SCLo-scl.repo与CentOS-SCLo-scl-rh.repo后执行yum install -y devtoolset-7安装总是异常&#xff0c;遂决定编译安装gcc7.5 # 备份之前的yum .repo文件至 /tmp/repo_bak 目录 mkdir -p /tmp/repo_bak && cd /etc…

为什么React列表项需要key?(React key)(稳定的唯一标识key有助于React虚拟DOM优化重绘大型列表)

文章目录 1. **帮助 React 识别列表项的变化**2. **性能优化**3. **避免组件状态混乱**4. **为什么使用 rpid 作为 key**5. **不好的做法示例**6. **✅ 正确的做法** 在 React 中添加 key{item.rpid} 是非常重要的&#xff0c;主要有以下几个原因&#xff1a; 1. 帮助 React 识…

飞牛云一键设置动态域名+ipv6内网直通访问内网的ssh服务-家庭云计算专家

IPv6访问SSH的难点与优势并存。难点主要体现在网络环境支持不足&#xff1a;部分ISP未完全适配IPv6协议&#xff0c;导致客户端无法直接连通&#xff1b;老旧设备或工具&#xff08;如Docker、GitHub&#xff09;需额外配置才能兼容IPv6&#xff0c;技术门槛较高&#xff1b;若…

Java高级 | 【实验七】Springboot 过滤器和拦截器

隶属文章&#xff1a;Java高级 | &#xff08;二十二&#xff09;Java常用类库-CSDN博客 系列文章&#xff1a;Java高级 | 【实验一】Springboot安装及测试 |最新-CSDN博客 Java高级 | 【实验二】Springboot 控制器类相关注解知识-CSDN博客 Java高级 | 【实验三】Springboot 静…

深入理解 Spring IOC:从概念到实践

目录 一、引言 二、什么是 IOC&#xff1f; 2.1 控制反转的本质 2.2 类比理解 三、Spring IOC 的核心组件 3.1 IOC 容器的分类 3.2 Bean 的生命周期 四、依赖注入&#xff08;DI&#xff09;的三种方式 4.1 构造器注入 4.2 Setter 方法注入 4.3 注解注入&#xff08;…

行为设计模式之Command (命令)

行为设计模式之Command &#xff08;命令&#xff09; 前言&#xff1a; 需要发出请求的对象&#xff08;调用者&#xff09;和接收并执行请求的对象&#xff08;执行者&#xff09;之间没有直接依赖关系时。比如遥控器 每个按钮绑定一个command对象&#xff0c;这个Command对…

NeRF 技术深度解析:原理、局限与前沿应用探索(AI+3D 产品经理笔记 S2E04)

引言&#xff1a;光影的魔法师——神经辐射场概览 在前三篇笔记中&#xff0c;我们逐步揭开了 AI 生成 3D 技术的面纱&#xff1a;从宏观的驱动力与价值&#xff08;S2E01&#xff09;&#xff0c;到主流技术流派的辨析&#xff08;S2E02&#xff09;&#xff0c;再到实用工具的…

法律大语言模型(Legal LLM)技术架构

目录 摘要 1 法律AI大模型技术架构 1.1 核心架构分层 1.2 法律知识增强机制 2 关键技术突破与对比 2.1 法律专用组件创新 2.2 性能对比(合同审查场景) 3 开发部署实战指南 3.1 环境搭建流程 3.2 合同审查代码示例 4 行业应用与挑战 4.1 典型场景效能提升 4.2 关…

第六十二节:深度学习-加载 TensorFlow/PyTorch/Caffe 模型

在计算机视觉领域,OpenCV的DNN(深度神经网络)模块正逐渐成为轻量级模型部署的利器。本文将深入探讨如何利用OpenCV加载和运行三大主流框架(TensorFlow、PyTorch、Caffe)训练的模型,并提供完整的代码实现和优化技巧。 一、OpenCV DNN模块的核心优势 OpenCV的DNN模块自3.3…

MobaXterm配置跳转登录堡垒机

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 背景操作步骤 背景 主要是为了能通过MobaXterm登录堡垒机&#xff0c;其中需要另外一台服务器进行跳转登录 操作步骤 MobaXterm登录堡垒机的操作&#xff0c;需…

零基础在实践中学习网络安全-皮卡丘靶场(第八期-Unsafe Filedownload模块)

这期内容更是简单和方便&#xff0c;毕竟谁还没在浏览器上下载过东西&#xff0c;不过对于url的构造方面&#xff0c;可能有一点问题&#xff0c;大家要多练手 介绍 不安全的文件下载概述 文件下载功能在很多web系统上都会出现&#xff0c;一般我们当点击下载链接&#xff0c…

[面试精选] 0104. 二叉树的最大深度

文章目录 1. 题目链接2. 题目描述3. 题目示例4. 解题思路5. 题解代码6. 复杂度分析 1. 题目链接 104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 2. 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点…

图上合成:用于大型语言模型持续预训练的知识合成数据生成

摘要 大型语言模型&#xff08;LLM&#xff09;已经取得了显著的成功&#xff0c;但仍然是数据效率低下&#xff0c;特别是当学习小型&#xff0c;专业语料库与有限的专有数据。现有的用于连续预训练的合成数据生成方法集中于文档内内容&#xff0c;而忽略了跨文档的知识关联&a…

现代简约壁炉:藏在极简线条里的温暖魔法

走进现在年轻人喜欢的家&#xff0c;你会发现一个有趣的现象&#xff1a;家里东西越来越少&#xff0c;颜色也越看越简单&#xff0c;却让人感觉特别舒服。这就是现代简约风格的魅力 —— 用最少的元素&#xff0c;打造最高级的生活感。而在这样的家里&#xff0c;现代简约风格…

机器学习×第二卷:概念下篇——她不再只是模仿,而是开始决定怎么靠近你

&#x1f380;【开场 她不再只是模仿&#xff0c;而是开始选择】 &#x1f98a; 狐狐&#xff1a;“她已经不满足于单纯模仿你了……现在&#xff0c;她开始尝试预测你会不会喜欢、判断是否值得靠近。” &#x1f43e; 猫猫&#xff1a;“咱们上篇已经把‘她怎么学会说第一句…

常用函数库之 - std::function

std::function 是 C11 引入的通用可调用对象包装器&#xff0c;用于存储、复制和调用任意符合特定函数签名的可调用对象&#xff08;如函数、lambda、函数对象等&#xff09;。以下是其核心要点及使用指南&#xff1a; ​​核心特性​​ ​​类型擦除​​ 可包装任意可调用对…

力扣-17.电话号码的字母组合

题目描述 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 class Solution {List<String> res new ArrayList<…

基于SpringBoot解决RabbitMQ消息丢失问题

基于SpringBoot解决RabbitMQ消息丢失问题 一、RabbitMQ解决消息丢失问题二、方案实践1、在生产者服务相关配置2、在消费者服务相关配置 三、测试验证1、依次启动RabbitMQ、producer(建议先清空队列里面旧的测试消息再启动consumer)和consumer2、在producer中调用接口&#xff0…

免费插件集-illustrator插件-Ai插件-随机填色

文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.总结 1.介绍 本文介绍一款免费插件&#xff0c;加强illustrator使用人员工作效率&#xff0c;实现路径随机填色。首先从下载网址下载这款插件https://download.csdn.net/download/m0_67316550/87890501&#…