目录
入门
前&后台进程
前台进程:
后台进程
常用命令
./XXX &
fg命令 & bg命令
Ctrl + c / Ctrl + \
信号的概念
信号的产生
1.键盘产生
2.系统调用指令
3.异常
4.软件条件
信号的保存
信号的处理
1.信号屏蔽字
2.未决信号表
3.信号处理函数表
四种情况
系统接口
1.signal
作用:
参数:
返回值:
2.sigprocmask
作用:
参数:
返回值:
3.sigempty及一系列
作用:
参数:
返回值:
4.sigpending
作用:
参数:
返回值:
5.sigaction
作用:
参数:
返回值:
入门
在生活角度上
- 在网上买了很多商品,在等待不同商品快递的到来;但就即使快递没有到来,你也知道快递来临时,你该怎么处理快递,也就是你能“识别快递”
- 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需要 5分钟之后才能去取快递;那么在这 5分钟内,你并没有下楼取快递,但是你知道有快递来了;也就是取快递的行为不一定要立即执行,可以理解为“在合适的时候去取”
- 在收到通知,再到你拿到快递期间,是有一个时间窗口的;在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了;本质上是你“记住了有一个快递要去取”
- 当你时间合适,顺利拿到快递之后,就要开始处理快递了;而处理快递的方式一般有三种:1.执行默认动作(幸福的打开快递,使用商品);2.执行自定义动作(快递是零食,你要送给你的女朋友);3.忽略快递(快递拿上来之后,扔到床头,继续开一把游戏)
- 快递到来的整个过程,对你来讲是异步的,你不能确定快递员什么时候到来
在技术角度上
- 用户输入命令,在Shell下启动一个前台进程:用户按下Ctrl+c,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程因为收到信号,进而引起进程退出;
- 将Ctrl+c 信号处理的过程和生活的例子相结合,我们可以这么理解:进程就是你,操作系统就是快递员,信号就是快递
前&后台进程
刚才我们说到,Ctrl+c是发给前台进程的,那么什么是前台进程,什么是后台进程呢?
前台进程:
- 前台进程是指正在终端(或控制台)上活动的进程,它会占用终端的输入和输出;当用户在终端中启动一个进程时,默认情况下该程序将在前台运行;
- 前台进程会接受用户输入,并将输出显示在终端上,用户可以与其进行交互,可以通过按下Ctrl+c来发送中断信号(SIGINT)给前台进程,以终止它的执行
后台进程
- 后台进程是指在终端上运行,但不会占用终端输入和输出的进程;它们在后台默默地执行,用户不会直接与其交互
- 用户可以通过在命令的末尾添加“&”符号来将进行置于后台运行;例如,./my_program & 将会让my_program进程在后台运行
- 用户 也可以使用 bg命令将一个已经在前台运行的进程移到后台继续执行,或使用fg命令将一个后台进程切换到前台
所以,在Linux系统中,前台进程和后台进程是指进程在终端中的运行状态
常用命令
./XXX &
在进程后面加上 & 符号可以让进程置于后台运行,下面我举个简单的例子
可以发现,在将进程置于后台运行后,我们执行Ctrl+c,并不会中断进程
并且我们在终端输入 ll 命令也能够正常执行,这就代表进程已经在后台运行了,我们可以使用kill -9将其终止
fg命令 & bg命令
- fg命令:fg命令可以将一个后台进程放到前台运行,fg x (x为进程的作业号)
- bg命令:bg命令可以将一个前台进程移动到后台运行,bg x;
- 作业号:在终端使用jobs命令,可以查看当前运行的作业。
Ctrl + c / Ctrl + \
Ctrl + c :向前台进程发送2号中断信号,默认情况会中断前台进程;
Ctrl + \ :向前台进程发送3号退出信号,默认情况会终止前台进程;
信号的概念
在计算机科学中,信号是用于通知进程发生了某些事件的一种机制。这些事件可以是各种各样的,包括外部硬件事件(如键盘输入)、软件事件(如某个进程终止)、或者由操作系统或其他进程生成的事件。
下面是一些常见的信号
- SIGHUP(1):挂起信号,通常在终端连接断开时发送给进程。
- SIGINT(2):中断信号,通常由用户键入 Ctrl+C 时发送给前台进程组中的所有进程。
- SIGQUIT(3):退出信号,通常由用户键入 Ctrl+\ 时发送给前台进程组中的所有进程,用于请求进程退出并生成核心转储。
- SIGKILL(9):强制终止信号,用于强制终止进程。
- SIGTERM(15):终止信号,通常用于请求进程正常终止。
- SIGSTOP(19):停止信号,用于暂停进程的执行。
- SIGCONT(18):继续信号,用于恢复被停止的进程的执行。
除了上述常见的信号外,还有其他信号,每个信号都有一个唯一的编号。当进程接收到一个信号时,它可以选择忽略、捕获、或者使用默认行为处理该信号,可以通过kill -l查看所有信号
信号的产生
1.键盘产生
当进程运行时,我们可以在终端上按下按键来直接向进程发送信号,SIGINT的默认处理动作是中断进程,SIGQUIT的默认处理动作是终止进程并core dump,这里我们就要说一下core dump了。
core dump:
核心转储(core dump):是指在程序因为异常情况(如段错误、非法指令的能)而终止时,操作系统将当前程序的内存状态保存到一个称为核心转储文件(core dump file)的文件中,这个文件包含了程序崩溃时的内存映像,通常包括了程序的代码、数据、堆栈等信息。
核心转储文件对于调试程序异常非常有用,因为它提供了程序崩溃时的内存状态快照,可以帮助开发人员确定程序崩溃的原因。开发人员可以使用调试工具(如GDB)加载核心转储文件,并查看程序崩溃时的内存状态,从而定位和修复问题。
一般情况下,系统设置是不开启核心转储文件的,我们需要通过ulimit -a进行查看
core file size就是核心转储文件的大小,通常系统会将其设置为0,我这里是将其设置成unlimited了。我们可以输入ulimit -c 1024,设置其大小,然后运行一个异常的文件,就会生成核心转储文件,如果运行之后还是没有生成核心转储文件,可以参考下面的博客:
http://t.csdnimg.cn/TVIyg
2.系统调用指令
我们可以使用kill函数向进程发送信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid是进程的唯一标识符,sig是想要向pid对应进程发送的信号编号。
也可以在终端中使用kill指令向进程发送信号
kill -sig pid
3.异常
void handler(int signo)
{
std::cout<<"get a signo:"<<signo<<std::endl;
sleep(1);
}
int main()
{
signal(8,handler);
int a = 10;
a/=0;
return 0;
}
signal函数是用来设置信号处理的函数,它会捕获8号信号,并运行handler函数,而不会执行原来8号信号的默认处理方式。
注意:不是所有的信号被signal函数捕获后,都不去执行默认处理方式,比如9号信号。
运行这个程序,当a /= 0时,程序会报错,然后被终止,下面我们讲一下为什么程序会被终止。
在现代计算机架构中,CPU通常会在运行计算式维护一系列的状态寄存器,其中包括一个溢出标志位( Overflow Flag)。这个溢出标志位用来指示在运算过程中是否发生了溢出。
溢出标志位在进行运算时会根据结果是否发生了溢出而被设置或清除。当发生溢出时,溢出标志位会被设置为1;否则,被清除为0。程序可以通过检查溢出标志位来判断运算结果是否溢出,以便进行相应的处理,比如错误处理或者调整数据类型以避免溢出。
我们运行这个程序,当a / 0的时候,发生溢出,溢出标志位被设置为1,然后CPU通知操作系统,操作系统解释为kill(targetprocess,signo),然后操作系统将进程中存储信号的表格中,这个表格我们接下来会讲。
4.软件条件
Linux 中的软件条件指的是在 Linux 操作系统下运行的软件所需的系统要求和依赖条件。这些条件可能包括硬件要求、操作系统版本、库文件、依赖软件等。通常情况下,开发者在发布软件时会提供相关的系统要求和依赖信息,以确保用户能够顺利地在他们的 Linux 系统上安装和运行该软件。
操作系统中的时间
- 所有用户的行为,都是以进程的形式在OS中表现的
- 操作系统只要把进程调度好,就能完成所有的用户任务
- CMOS中的实时时钟芯片,周期性、高频率的向CPU发送时钟中断
补充:CMOS和实时时钟芯片
CMOS是一种电路技术,用于制造半导体集中电路。在计算机系统中,CMOS经常用于描述存储BIOS设置和实时时钟的特殊RAM区域,这种CMOS RAM通常由一个电池供电,即使计算机关闭,也能保持存储的数据不丢失。CMOS RAM中存储的信息包括系统的基本配置、日期和时间等。
实时时钟芯片(RTC芯片)通常包含于CMOS芯片中,用于生成系统时钟信号和实时时间。RTC芯片会生成时钟中断信号,然后通过主板上的控制器发送给CPU,CPU在接收到时钟中断信号后,会暂停当前任务并执行中断任务。
信号的保存
概念
- 实际执行信号的处理动作叫做信号的递达
- 信号从产生到递达之间的状态,称为信号未决
- 进程可以选择阻塞某个信号
- 被阻塞的信号产生时将保持在未决状态,直接进程解除对该信号的阻塞
信号的递达又可以分为
- 信号的忽略
- 信号的默认
- 信号的自定义捕捉
信号的处理
信号的处理分为三步
1.信号屏蔽字
- 信号屏蔽字是一个位掩码,用于表示进程当前正在阻塞的信号集合。
- 当信号的对应位被设置为1时,表示该信号被阻塞。被阻塞的信号不会被递达。
- 进程可以通过系统调用 sigprocmask() 来设置和修改信号屏蔽字,以阻塞或解除阻塞特定的信号。
2.未决信号表
- 未决信号表是一个数据结构,用于记录当前进程正在等待处理的信号。
- 通常,它是一个位图,每个位对应一个可能的信号,被设置位1表示相应的信号是未决的,即已经发送给进程但尚未被处理;被设置0代表该信号已经被处理或从未发送给进程
- 进程在运行时会定期检查未决信号表,并且根据每个信号的处理方式来处理这些未决信号。
3.信号处理函数表
- 信号处理函数表是一个数据结构,用来存储每个信号对应的处理函数。
- 通常,它是一个函数指针数组,里面的每个函数指针对应一个信号,呈强相关
- 当进程收到一个信号时,会根据该信号的索引在处理函数表中查找相应的处理函数,并执行
经过上面三步,一个信号就可以被信号接收并处理,图解可以为:
四种情况
根据信号屏蔽字和未决信号表,我们可以找出一个信号在进程中的四种情况
1.信号屏蔽字中的位为1 ,未决信号表中的对应位为1
- 这种情况表示该信号被阻塞,并且当前进程有一个未决信号,等待进程解除信号的阻塞后,该信号会被递送给进程。
2.信号屏蔽字中的位为1 ,未决信号表中的对应位为0
- 这种情况表示该信号被阻塞,但是当前进程没有一个未决信号等待被处理。
3.信号屏蔽字中的位为0 ,未决信号表中的对应位为1
- 这种情况通常不会出现,因为信号被发送给进程时,如果信号未被阻塞,,则会被立即添加到未决信号表中。
4.信号屏蔽字中的位为0 ,未决信号表中的对应位为0
- 这种情况表示该信号既没有被阻塞,也没有未决状态,即该信号从未被发送给进程或者已经被处理。
系统接口
1.signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
作用:
signal函数的功能是设置在接收到特定信号时要调用的处理函数,它的行为有三种不同的方式
- 如果handler参数指定为SIG_DEL(默认处理方式),则该信号的默认处理方式将被恢复
- 如果handler参数指定为SIG_IGN(忽略该信号),则进程将忽略该信号,不做任何处理
- 如果handler参数是一个有效的函数指针,那指定的函数将成为接收到信号时的处理函数。
参数:
- ing signum:代表信号编号
- sidhandler_t handler:可以为SIG_DEL、SIG_IGN或一个有效的函数指针
返回值:
如果函数执行成功, 那么将返回之前的信号处理函数指针的值,否则返回SIG_ERR。
2.sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
作用:
sigprocmask函数用来检查和修改当前进程的信号屏蔽字
参数:
- int how:指定了函数的行为,可以是以下三值之一
SIG_BLOCK
:将set
指定的信号集合添加到当前信号屏蔽字中。SIG_UNBLOCK
:从当前信号屏蔽字中移除set
指定的信号集合。SIG_SETMASK
:设置当前信号屏蔽字为set
指定的信号集合。- const sigset_t *set:指向要修改的新信号屏蔽字的指针。如果
how
是SIG_SETMASK
,那么该参数指定了要设置的新信号屏蔽字。如果how
是SIG_BLOCK
或SIG_UNBLOCK
,那么该参数指定了要添加或删除的信号集合。- sigset_t *oldset:指向用于存储之前信号屏蔽字状态的指针。如果不为
NULL
,则将当前信号屏蔽字的状态存储在oldset
指向的位置。
返回值:
成功返回0,失败返回-1,并设置错误码。
3.sigempty及一系列
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
作用:
sigemptyset(sigset_t *set)
:将信号集合清空,即设置所有位为 0。sigfillset(sigset_t *set)
:将信号集合填满,即设置所有位为 1。sigaddset(sigset_t *set, int signum)
:将指定信号加入到信号集合中。sigdelset(sigset_t *set, int signum)
:将指定信号从信号集合中删除。sigismember(const sigset_t *set, int signum)
:检查指定信号是否在信号集合中。
参数:
- sigset_t *set:信号集合位图
- signum:信号编号
返回值:
sigsimember如果信号存在信号集合中,返回1,否则返回-1,并设置错误码;
其他几个成功返回0,否则返回-1,并设置错误码。
4.sigpending
#include <signal.h>
int sigpending(sigset_t *set);
作用:
获取当前进程的未决信号集
参数:
- sigset_t *set:信号集合
返回值:
成功返回0,失败返回-1,并设置错误码
5.sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
作用:
sigaction
是一个用于检查和修改信号处理方式的函数,它允许程序员更加灵活地处理信号
参数:
- int signum:信号编号
- const struct sigaction *act:结构体指针,用来设置新的信号处理方式
- struct sigaction *oldact:结构体指针,用于储存之前的信号处理方式,不需要可以传入nullptr
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
是一个函数指针,指向信号处理函数。如果sa_flags
中没有设置SA_SIGINFO
标志,则使用sa_handler
;否则,使用sa_sigaction
。sa_mask
是一个sigset_t
类型的信号屏蔽字,用于设置在执行信号处理函数时需要阻塞的其他信号。sa_flags
是一个标志位,用于指定额外的处理选项,比如SA_RESTART
标志表示在接收到信号后自动重启系统调用。sa_sigaction
是一个函数指针,指向带有三个参数的信号处理函数。这个函数可以获取有关信号的更多信息,如信号的来源等。
返回值:
成功返回0,失败返回-1,并设置错误码
这就是我们关于LINUX信号部分的讲解了,喜欢这篇博客的点个赞~