系统编程--信号

信号的概念

特点

在这里插入图片描述
在这里插入图片描述
注意:所有信号的产生及其处理都是内核完成,所以,后续我们做的任何有关信号的代码操作,都不是我们直接去创建一个信号,或者操作一个信号,而是驱使内核去创建、操作信号

与信号相关的事件和概念

产生信号

在这里插入图片描述
以上五种方式均可以产生信号

信号状态、处理方式

在这里插入图片描述
第一个:递达,意思是信号产生后,并成功的送达
第二个:未决,意思是在“信号产生之后”,以及“信号成功到达目标进程”之间的状态,我们称之为“未决”
产生未决可能是由于阻塞,导致信号未到达(而产生阻塞的原因是目标进程对该信号进行了屏蔽)。或者其他原因导致信号已经发出,但是未到达

第三个:信号的处理方式
其中:捕捉,是开发者捕捉到该信号,表示捕捉到信号后,执行指定的代码(也可以说是自定义

信号屏蔽字、未决信号集

在这里插入图片描述
在这里插入图片描述
首先,这两个东西本质上都是位图(所有位置默认是0),存在于一个进程的进程控制块:PCB中

假如说此时当前进程被Ctrl + C了,而Ctrl + C是一个2号信号,此时未决信号集的2号位置就会被翻转为1,这时,进程会去查看信号屏蔽字,看到信号屏蔽字的2号位置是0,也就是默认状态,那么进程就会去立马处理未决信号集中的2号信号,处理完毕之后,将其改回0

而如果此时信号屏蔽字上的2号位置是1:
在这里插入图片描述如果此时信号屏蔽字上的2号位置是1,表示当前进程对2号信号屏蔽,那么2号新号就会被阻塞,无法递达,所以该信号就会一直处于未决状态,直到信号屏蔽字中的2号位置被改回0,2号信号被解除屏蔽,进程才会去处理2号信号,解除其未决状态

总结

在这里插入图片描述

信号编号

使用命令查看系统信号

在这里插入图片描述
使用命令:kill -l (小L),可以查看当前系统的所有信号

每个信号都有四要素:
在这里插入图片描述
其中,前31个信号,是常规信号,有默认的事件和默认处理动作
事件:每个信号的触发都会对应一个事件的发生,只有该事件发生了,内核才会向进程发送对应的信号(递送,不一定递达)
默认处理动作:即接收到该信号后,默认如何处理该信号,或者后续进程会做什么动作

后面的信号是实时信号,没有事件和默认处理动作,一般很少用到

查看信号四要素

使用man 7 signal即可查看
在这里插入图片描述
在这里插入图片描述
第一列是信号名(一系列宏)
第二列是信号编号,可以看到有些信号不止一个编号,因为那是区分不同的操作系统下对应不同的信号编号
第三列是默认处理动作
第四列是内核向进程发送该信号的事件

常用信号对应的事件

在这里插入图片描述
在这里插入图片描述
注意四个信号:
一个是SIGKILL、一个是SIGSTOP,这两个信号不会被丢弃、忽略,也不会被阻塞、屏蔽,而是必须处理,且必须终止进程/暂停进程

另外,系统提供了两个可以由程序员自定义事件的信号,也就是这两个信号可以由程序员决定产生什么事件向当前进程发送该信号,其默认处理动作是终止进程

在这里插入图片描述

信号的产生

终端按键产生信号、硬件异常产生信号

在这里插入图片描述

kill命令/kill函数产生信号

使用命令发送信号

在这里插入图片描述
即 kill -信号编号 进程id

注意:虽然该命令叫kill,但是他不仅仅可以发送SIGKILL信号,他是所有信号的发送命令,只不过大部分的命令默认执行动作是终止进程,所以叫做kill

其中,信号编号可以自己决定,如下演示:

1、首先我们有一个死循环的进程:
在这里插入图片描述

2、在另一个终端使用:ps aux查看所有进程的信息:
在这里插入图片描述
3、找到我们那个进程的进程ID,并向其发生信号:
在这里插入图片描述
我们发送信号11,效果:
在这里插入图片描述
可以看到,该进程就收到了段错误的信号,并且执行了默认处理动作,将进程终止

假如我们发送信号7,效果:
在这里插入图片描述

同样,我们要终止进程时,可以使用kill -9 pid
去无条件终止进程

使用函数发送信号

在这里插入图片描述
注意:虽然函数名叫kill,但是他的功能是发送一个信号

代码:
在这里插入图片描述
上述代码子进程使用kill函数向父进程发送SIGKILL信号,使得父进程终止

而如果父进程的死循环中,一直在输出数据,那么可能由于IO操作,使得CPU持续被占用在IO,导致父进程没有CPU时间片去处理SIGKILL信号,导致父进程不会被终止,所以要么在死循环中把输出去掉,要么加个sleep(当然在实际项目中肯定不会一直输出,所有该情况在实际项目中不会出现)

注意:向kill函数传参数二时,传信号的宏,这样可以避免不同操作系统下同一个信号的编号不同

kill函数补充

在这里插入图片描述
当传入pid=0时,会将当前调用kill的进程所在的进程组内所有的进程都发送信号
当传入pid<-1时,这个<-1,实际上是某个进程组id的取负,将该进程组id加上负号传入后,会向该进程组内的所有进程发送信号

该效果还可以使用命令实现:kill -信号名 pid(-组id),如下:
在这里插入图片描述
使用命令ps ajx,左边数第一列是父进程id, 第二列是进程id,第三列是组id,第四列是会话id
之后使用kill,发送终止信号,给10698这个组,而一般组id就是该组的父进程id

当传入pid = -1时,会向当前系统下所有有权限的进程发送信号

alarm闹钟

简介

在这里插入图片描述
设置定时器,首先需要注意:
1、每个进程只有一个定时器,这个定时器是当前这个进程中所有函数所共享的,所以每次在同一个进程中做的闹钟操作,都是对同一个定时器做的操作
2、当调用alarm时,会返回上一次计时器剩余未读秒的秒数
其中,alarm(0),在返回上一次计时器剩余秒数的同时,设置定时器定时0秒,实际上实现的效果就是取消定时器
当首次调用alarm时,会返回0
3、定时器独立于进程的状态,他独立运行,总是在计时

代码:实现练习:
在这里插入图片描述
alarm(1),定时1秒后,向当前进程使用默认动作,即终止进程,且alarm不会影响后续代码的执行

补充:
在这里插入图片描述
使用time可以查看程序执行的时间

操作:
1、运行程序前加time:
在这里插入图片描述
输出:
在这里插入图片描述
real表示运行时间 user表示用户占用的时间 sys表示内核调用时间
可以看到,user + sys 的结果是0.3秒左右,而总时间是1秒左右,说明有0.7秒被用在了别的地方,而一般是用在了与控制台进行IO,所以一秒的程序,大量的时间会被控制台等设备占用,从而使得user + sys的时间缩短,该时间是程序真正的代码起作用的时间

而如果想优化程序,也就是让user + sys的时间在real中的占比尽可能的大,也就是在真实的时间内,让设备IO的时间减少,让代码运行的时间拉长,可以使用文件IO代替设备IO:
在这里插入图片描述
可以看到,如果重定向到out.txt文件中,那么程序就不会浪费大量的时间在等待IO上,最终user + sys使用了0.7秒左右,提高了程序的运行效率

最后文件中一秒内输出了:
在这里插入图片描述
九百多万

总结

在这里插入图片描述

settitimer闹钟

简介

在这里插入图片描述
参数一:可以指定定时的方式,分别是自然计时、用户区计时、用户+sys计时

参数二:传入一个结构体的地址,这个参数表示定时的时长
参数三:传入一个结构体的地址,这个参数是传出参数,表示剩余未读秒的时间
具体结构如下:
在这里插入图片描述
对于new_value、old_value的类型struct itimerval,其结构体内有两个成员,这两个成员也都是结构体(都为struct timeval类型:该子结构体有两个成员,分别表示秒和微秒),其中,it_interval结构体用来设置两次定时任务之间的间隔,it_value结构体用来设置定时时长

代码

在这里插入图片描述
首先介绍一下信号捕捉函数,这个函数的作用是当当前进程捕捉到了SIGALRM信号后,不执行默认动作,而是执行我们指定的函数

之后,创建两个大结构体,这两个结构体分别为settitimer的传入和传出参数,我们只初始化传入参数即可

设置传入参数的it_value结构体为2秒,it_interval结构体为5秒

最后检查返回值为-1,则失败

效果:
首先会执行一次it_value的定时
之后会持续执行it_interval的定时

即,先定时2秒后,打印hello world
之后每次都会定时5秒后,打印hello world

补充

在这里插入图片描述
了解即可

信号集操作函数

背景

在这里插入图片描述
由前面我们已经知道,当前进程的进程控制块中存放着信号屏蔽字和未决信号集两个位图,其中当信号发过来时,未决信号集会将对应的位变为1,此时会去查看信号屏蔽字,如果对应的1位是0,表示没有屏蔽,那么当前进程就会处理信号,执行默认动作,如果为1,表示屏蔽,那么当前进程就不会处理信号,该信号就变为了未决信号,直到屏蔽字中对应的位被改回0,当前进程才会去处理

而未决信号集我们无法手动控制,但是我们可以控制信号屏蔽字,进行信号的控制

但是由于信号屏蔽字在PCB里面,所以linux内核并不希望我们可以直接对其进行修改,所以,我们同样可以创建出一个位图,或者说位集合,对信号屏蔽字进行或操作,或者直接将其替换:
在这里插入图片描述

相关函数

设置set集合

在这里插入图片描述
上图中的set是名词,集合的意思

sigset_t是unsigned long类型

其中,对于第3、4个函数,表示将某个信号加入或者清出信号集,参数一传set信号集,参数二传信号的宏名称即可,因为宏对应的就是某个信号的编号,也就是在set集中的对应位置,或者说函数会自动找到宏对应的信号位置

操作set与信号屏蔽字做操作

在这里插入图片描述
我们不能直接拿到信号屏蔽字去做相关的操作,而是使用一个函数:sigprocmask
参数一:设置如何进行操作
1、阻塞,即添加阻塞,即添加屏蔽,他会将set中值为1的信号,对应到信号屏蔽字中,对相应的信号进行屏蔽设置
2、非阻塞,即解除阻塞,即解除屏蔽,他会将set中值为1的信号(注意此处虽然是解除,但还是找set值为1的位,因为在set中的1只是一个标记,表示要操作的信号,并不代表是否屏蔽或者解除屏蔽),对应到信号屏蔽字中,对相应的信号进行屏蔽解除
3、直接将set集合替换PCB中的原始屏蔽字集合
参数二:set集合
参数三:传出参数,旧的信号屏蔽集

读取未决信号集

在这里插入图片描述
我们虽然不能操作未决信号集,但是我们可以读取查看未决信号集的内容

参数为传出参数,传出未决信号集的内容

返回值:成功0,失败-1errno

总结

在这里插入图片描述

代码

原理

在这里插入图片描述
上图的所有操作,是在内核发送信号之前完成的,因为设置信号屏蔽,肯定要在内核发送信号之前,不然是无法屏蔽的

在这里插入图片描述
此时,如果进程接收到对应的信号,不会去进行处理,那么在该信号就会一直在未决信号集里,我们可以使用sigpending来查看未决信号集的内容

实现

在这里插入图片描述
首先我们可以自己定义一个打印位图的函数,方便进行未决信号集的打印,其中用到了sigismember这个函数,去查看对应信号编号的信号是否在某个集合中,如果在,则打印1,不在,则打印0

由此也可以推断,信号集中的信号就是按照编号进行编排的

在这里插入图片描述
首先创建set
之后将其清空,此时set中全是0
然后将对应的信号加到set中,此时SIGINT对应的信号被设置为1

之后使用sigprocmask,将set中对应设置为1的信号,对应到信号屏蔽字中,设置屏蔽

之后一个while循环,循环查看未决信号集的内容,并打印

效果:
在这里插入图片描述
刚开始没有发送任何信号,未决信号集都是0,但是此时信号屏蔽字中的2号位置已经被设置了屏蔽

之后,向进程发送Ctrl + C信号,2号信号发送给进程,但是由于该进程被设置了屏蔽,所以不会进行处理,那么该信号就会一直处于未决,所以,Ctrl+ C之后,未决信号集的2号位置就一直为1

我们可以使用Ctrl + \,终止进程

此时我们只需要加一行语句,就可以把Ctrl+\也设置屏蔽:
在这里插入图片描述
效果:
在这里插入图片描述
现在Ctrl + C 和 Ctrl + \都无法终止进程了,我们只有再开一个终端,使用kill -9 强制杀死

在这里插入图片描述

补充

验证9号和19号信号无法被屏蔽:
在这里插入图片描述
我们验证一下9号

效果:
在这里插入图片描述

信号捕捉

函数总览

在这里插入图片描述
有两个信号捕捉函数,第一个函数兼容性没有第二个函数的兼容性大,所以择机选择

signal函数

简介

在这里插入图片描述
signal函数,作用是注册一个信号捕捉函数,只是做了一个备案,其真正捕捉信号的是内核,要等信号发送过来内核才会捕捉,并调用相应的处理函数

原型

在这里插入图片描述
首先看typedef 后面的内容:不看typedef,后面是一个函数指针,该指针指向返回值为void 参数是一个int型参数的函数
而加上typedef之后,表示该函数指针自成一个类型,即sighandler_t是一个类型,表示返回值是void,参数是一个int型变量的函数

对于signal,参数一是信号宏,参数二是捕捉信号完之后做的操作

代码

在这里插入图片描述
main函数上面是一个捕捉后的处理函数

之后,在main函数内,首先使用signal注册捕捉信号和函数,之后进入while循环,循环打印fine

运行程序:
可以看到,我们向进程发生Ctrl + c信号,那么程序会放下手头的事情,暂时不打印fine,然后去执行信号捕捉处理函数,执行完之后,又继续手头的事情

最后使用Ctrl+z进程终止

sigaction

简介

在这里插入图片描述
该函数的兼容性更大,且他也是注册一个信号捕捉函数,而不是真实的捕捉信号,捕捉信号是交给内核处理的

原型

在这里插入图片描述
参数一,是要注册的捕捉信号
参数二,是一个传入参数,一个struct sigaction结构体地址
参数三,是一个传出参数,同样是struct sigaction结构体,表示旧的信号处理

在这里插入图片描述

对于sa_mask,他是一个sigset_t的一个集合,但是,他与系统级的mask不同的是,系统的进程的屏蔽字是整个程序声明周期,而这里的sa_mask屏蔽字只在信号捕捉处理函数执行期间生效,他会配合sa_flags,使得当信号捕捉处理函数执行期间,对所捕捉的信号屏蔽,比如,该函数是2号信号处理函数,那么在2号信号处理函数执行期间,会屏蔽2号信号,避免在其执行期间由于再次收到2号新号使得处理函数被重新从头执行:
在这里插入图片描述

代码

在这里插入图片描述
回调函数即信号处理函数,之所以叫回调,是因为该函数不是程序员调用的,而是达到某个条件后程序去调用的

同样,我们加一行sigaction,进行多个注册,也可以完成上面signal那样多个信号捕捉处理的操作:
在这里插入图片描述
之后可以在信号捕捉处理函数中,对捕捉到的信号编号进行判断,就可以区分捕捉到的是哪个信号了,如下图:
在这里插入图片描述

且该函数在处理函数执行期间如果再次收到信号,会将其阻塞,等待当前处理函数执行完毕后再去处理第二次收到的捕捉信号,如下“信号捕捉特性”详解

信号捕捉特性

简介

在这里插入图片描述

代码:信号捕捉函数执行期间,对捕捉到的信号进行自动屏蔽

在这里插入图片描述
我们让信号处理函数sleep十秒,这样可以模拟信号处理函数执行时间很长的场景

效果:
在这里插入图片描述
首先main函数循环一直在打印fine,之后,我们发送2号信号,main循环暂停,进入信号处理函数
信号处理函数捕捉到信号后,在该函数执行期间(十秒内),我们再次发送信号,可以看到没有任何的反应,因为此时2号信号处于屏蔽状态,而10秒之后,信号处理函数执行完毕,mask被恢复,那么内核会处理之前被屏蔽的2号信号,再次进入信号捕捉函数,最后这次函数执行完之后,继续main的fine打印

其中,在信号处理函数执行期间,不管内核向进程发送多少次2号新号,内核最终只会处理一次

**ps:**如果此时注册了两个信号:2、3的捕捉
那么在2被捕捉到之后,执行回调函数期间,2信号被屏蔽,但是3信号不会被屏蔽,此时如果发送信号3,那么回调函数会对信号3重新执行回调,信号2的回调会暂时搁置,等3信号处理完毕,才会继续处理2信号

补充:在信号捕捉函数执行期间,对其他未捕捉的信号屏蔽

在这里插入图片描述
我们将注册时使用的act的sa_mask,添加上想要在“捕捉处理函数执行期间”屏蔽的未捕捉的信号,这样,当sa_mask生效时,那些未被捕捉但是被添加到sa_mask的信号也会被屏蔽

效果:
在这里插入图片描述
可以看到,这时不仅对2屏蔽,还对3屏蔽,而当回调处理完之后,恢复系统级mask,由于此时未注册3信号的捕捉,会执行进程终止

**ps:**如果此时注册了3信号的捕捉,同时在sa_mask中还加入了3信号的屏蔽:
在这里插入图片描述
可以看到,前面都一样,最后不是执行信号3的默认动作,而是信号3被捕捉,执行对应的捕捉处理函数

内核实现信号捕捉简析

在这里插入图片描述
首先程序运行在主流程,之后,因为某些契机,进入了内核,内核查看是否有信号发来,有(说明没有被阻塞),那么处理信号,如果该信号有自定义处理函数,那么再从内核区进入用户区调用处理函数,该函数执行完之后,再次回到内核(因为一个函数要返回到调用者,所以必须回到内核),之后内核再回到用户区继续执行上次没执行完的流程

SIGCHLD

产生条件

在这里插入图片描述

借助信号捕捉回收子进程

问题

在“系统编程–进程间通信”中“管道读写行为”的练习中,父进程由于调用了execlp函数,该函数执行成功之后就不再返回,因此父进程就没有机会回收子进程了,只能依赖于系统的隐式回收,这时,就可以使用信号,进行信号捕捉,从而调用到回调函数,进行子进程的回收,因为信号的优先级高,所以父进程被卡在execlp时,可以去执行回调函数

更多类似父进程无法跳出的场景,都可以使用该方法,使用信号捕捉进行子进程的回收

代码

在这里插入图片描述
在这里插入图片描述
首先在for循环中循环创建子进程
之后,如果pid == 0,表示是子进程,就可以break

最后,如果i == 5,表示是父进程,之后,我们在父进程中注册捕捉,捕捉的是SIGCHLD信号,因为子进程任何的信号变化都会向父进程发送SIGCHID

而之所以把注册捕捉放在i == 5语句内,是因为捕捉信号,处理信号,这些操作就是由父进程来处理,所以,要在父进程独有的地方,进行信号捕捉的注册

之后我们看处理函数,即每次触发信号,来到处理函数,父进程就会回收一个子进程

补充:我们将处理函数做了一些小的优化:
在这里插入图片描述
检查了一下返回值,并且将打印语句变得更显眼

效果:
在这里插入图片描述
多运行几次后,出现了只回收了一个子进程

原因:父进程在子进程结束之前,最早结束了,那么这些子进程就会被交给孤儿院回收,父进程已经结束,不会回收这些子进程
解决在这里插入图片描述
父进程最后加一个死循环,保证父进程不会提前结束

效果:
其中,有几次运行是正常的,父进程会把所有的子进程回收掉,
但是,偶尔会出现:
在这里插入图片描述
父进程少回收了一个子进程,且目前父进程并没有结束(因为有死循环)

使用ps sjx查看进程:
在这里插入图片描述
发现有一个僵尸进程,即进程终止,父进程没有来回收,此前我们的处理办法是将父进程kill掉,这样该僵尸进程就会变成孤儿进程,被孤儿院回收

产生僵尸进程的原因:
由于进程回收函数是通过捕捉信号来触发的,而每个子进程死亡向父进程发的是同一种信号,那么,当父进程收到子进程发来的信号后,处理函数被执行,执行过程中,如果有其他两个子进程死亡,向父进程发送同样的信号,但是这两个信号会被屏蔽,父进程执行完当前处理函数后,对于在执行期间发来的两个信号,只会处理一个,所以另一个就不会被回收,且已经终止,变为了僵尸进程

解决:
在这里插入图片描述
对wait进行循环,此时,哪怕执行期间有多个进程死亡,也会将这些子进程回收掉,此时这些后死亡的子进程被父进程回收依赖的就不是信号了,而是wait在系统级别的检查,他一旦检查到有子进程死亡,就会将其回收

其他优化
在这里插入图片描述
在子进程结束时,返回自己的 循环因子:i

在这里插入图片描述
捕捉处理函数中,我们可以使用waitpid,参数一、参数三分别传入-1,0,那么就跟wait一样了。。。

但是参数二可以传出进程退出时的返回值,我们可以使用一系列宏进程查看
这些宏的介绍在“系统编程–进程”中的“wait函数”下的“获取子进程退出状态”

问题再次产生:
在这里插入图片描述
由于捕捉信号的注册是在父进程中进行,假如说子进程在父进程把SIGCHLD注册完成之前就死亡了,那么此时,父进程收到SIGCHLD信号后会执行默认动作,默认动作是忽略,那么此子进程就不会被回收了

解决方案:
在这里插入图片描述
我们可以在fork之前,将SIGCHLD添加阻塞屏蔽,这样,信号发送到当前不管是父进程还是子进程都会阻塞等待,当父进程将捕捉信号注册完成之后,再将SIGCHLD的阻塞屏蔽解除,如果先前有子进程死亡,该信号在解除阻塞后会被处理

中断系统调用

介绍

在这里插入图片描述
在这里插入图片描述
对于一些阻塞式的系统调用,或者称为慢速系统调用,一旦他们在阻塞期间收到一个信号,使得系统调用被中断,那么他们将不会继续执行,也就是这时如果信号处理完毕,程序会从该系统调用的下一个语句开始执行,而忽略了他之前在阻塞等待数据的状态

而这种情况肯定不是我们想要的,我们想要在一些阻塞等待数据的系统调用在等待数据时被信号中断后,处理完信号还可以继续阻塞等待,那么这时我们可以将sigaction中的sa_flags参数设置为重启

这样的话,当系统调用被信号中断后,再次回到主流程,该系统调用会自动重启

补充

在这里插入图片描述
将sa_flags设置为SA_NODEFER,且sa_mask中不包含该信号,那么当前捕捉的信号在处理函数执行期间,就不会被屏蔽

进程组和会话

介绍

在这里插入图片描述
多个进程组成一个进程组

多个进程组组成一个会话

在这里插入图片描述
如上图,我们在两个终端分别开一个进程和多个进程

其中:从左到右依次是:父进程ID、进程ID、进程组ID、会话ID
首先看第一个组,其组长是14898,因为他的进程ID == 组ID
其他进程的进程组ID都是14898
整个进程组的父进程都是12889,即其中一个bash,且整个进程组的会话ID都是该bash
所以,整个进程组的父进程ID就是该进程组的会话ID

所以会话是依赖于bash建立的

创建会话的注意点

在这里插入图片描述
会话的创建者不能是进程组组长,且需要有root权限
而由于父进程与子进程之间,父进程是组长,所以,父进程无法创建会话,所以只能让父进程终止,子进程去创建会话

创建会话的进程会变成所创建的会话的首进程,且会成为该会话内的一个新进程组的组长(身兼二职)

getsid、setsid

简介

在这里插入图片描述
getsid就是获取一个进程的会话id
setsid就是进行会话的创建

接下来我们重点来看setsid

setsid

函数

在这里插入图片描述
他会将当前调用setsid的进程,设置为新会话的ID,以及该新会话内的一个组的组长,前提是调用setsid的进程不能是组长

代码

在这里插入图片描述
将子进程变为新会话的会长,以及该新会话内的新组的组长

其中,getpid()表示获取当前进程的进程ID
getpgid(pid),表示获取pid所在的进程组ID,传入0的话表示获取当前进程的进程组ID
getsid(pid),表示获取pid所在进程的会话ID,传入0的话表示获取当前进程的会话ID

效果:
在这里插入图片描述

补充:
在这里插入图片描述
在这里插入图片描述
在会话ID后面一列,表示的是终端,
其中有tty:文字终端、pts:图形化虚拟终端
而?则表示无终端

这也是创建会话的特性,新创建的会话无终端

守护进程

概念

在这里插入图片描述
守护进程就是后台进程,该进程不与用户进行直接交互,运行在系统的后台,且不受系统登录or注销(开关机)的影响,可以持续运行
一般服务器中的服务就是这样运行

创建守护进程

使用命令

使用代码

步骤分析

在这里插入图片描述
1、创建子进程、父进程退出
2、子进程创建会话
3、通常根据需要,改变当前工作目录:
将工作目录放在一个可靠的不可被卸载的目录,一般系统中的目录都是可靠的,只要不是在U盘目录即可
该步骤也是主要用于:如果程序一开始运行在U盘,那么就一定要进行工作目录的改变,不然U盘被程序占用,无法被拔掉
其中,改变工作目录:不会将可执行文件以及相应的需要的文件移动到指定目录下,且后续的资源路径要按照修改后的工作目录进行定位,这里改变文件工作目录的主要作用是,防止这样的长时间持续运行的进程占用可移动磁盘,导致可移动磁盘想移动而无法移动

4、通常根据需要,重设文件访问权限掩码:
使用umask()函数:
在这里插入图片描述
参数传入八进制权限数,如:0022,其中0前缀表示八进制数

该步骤主要为了防止进程来的权限掩码是经过改动的,会影响到当前新的会话

5、通常根据需要,关闭用不到的文件描述符:
在这里插入图片描述
因为是守护进程,用不到控制台进行数据交互,所以,可以将标准输入输出出错进行关系,但是这样的话如果我们使用open创建一个文件,返回的文件描述符是从0开始,这与我们的编程习惯相悖,所以,如果一个进程没有不需要控制台输入数据,也不需要向控制台输出数据,那么可以将标准输入输出出错重定向到“文件黑洞”,但是如果一个守护进程需要一些数据(比如日志输出),那么可以将其重定向到其他文件,比如重定向标准输出到一个日志文件

6、上面工作做完之后,就可以开始当前进程的业务逻辑了

总结:
在这里插入图片描述

代码

在这里插入图片描述
改变工作目录为用户目录
在这里插入图片描述
将标准输入关闭,此时fd应该是0,因为目前标准输入(0)被关闭,可用的最小的文件描述符就是0

标准输出出错都定向到文件黑洞

在之后就是业务逻辑了

但是注意,此时后续的资源定位如果使用相对路径,那么要在更改后的工作目录为参考点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值