进程

1 概述

1.1 背景

1.1.1 操作系统的背景

操作系统最初的原型是一种批处理系统(batch),操作员将所有的任务放在一起,由批处理系统进行读取并交给计算机执行,当一个任务执行完成以后,批处理系统会自动地取出下一个任务。在批处理系统中,存在的最严重的问题是任务执行的过程,会经常需要等待IO操作,这会导致CPU经常性地空闲。 一种解决方案是引入多道程序设计,在内存当中划分多个区域,每个区域存储一个任务的指令和数据,当其中一个任务在等待IO操作时,另外一个任务可以使用CPU。除了希望充分利用CPU资源,程序员还希望自己的对计算机的操作能够得到迅速的响应,这种需求导致了分时系统的诞生。在分时系统中,操作系统会给每个任务分配一些时间来使用CPU,这样CPU就不会沉浸在一个 大型的任务中,期间的用户操作也会很快地得到响应。而进程的概念是上述诸多操作系统能够正常运转的核心。

1.1.2 进程存在的意义

解耦

无论是批处理系统还是多道程序设计或者分时操作系统, 他们本质的思想就是把任务/程序并发执行,共享CPU和其他计算机资源。

为了实现这种技术,操作系统将每个正在执行的程序抽象为一个进程,程序员在书写---运行在操作系统上层的程序时,会把运行中的程序看成一个进程,认为每个进程拥有自己独立的CPU和内存资源(当然这种看起来的独占都是虚拟的)。

而操作系统的作用就是合理分配和调度真实、复杂的底层硬件资源,为进程提供逻辑上的支持。这样作为上层程序开发者, 我们只需要认为每个进程拥有自己独立的CPU和内存资源即可, 而操作系统负责基于这种设计逻辑去合理的调度和分配复杂的底层硬件资源。

1.1.3 进程的定义

从用户的角度来看,进程是一个程序的动态执行过程。是一个把静态程序文件加载进内存、分配资源、执行、调度和消亡的过程。

从操作系统的角度来看,进程是资源分配的基本单位进程需要占用CPU资源以执行程序指令;而除了需要占用CPU资源以外,进程还需要占据存储资源来保存状态。进程需要保存的内容包括数据段、代码段、堆、栈以及其他内存空间,进程也需要占用资源管理打开的文件、挂起的信号、内核内部数据、处理器状态、存在内存映射的内存空间以及执行线程,而执行线程的信息则包含程序计数器、栈和寄存器状态。

综上所述,所谓进程,就是处于执行期的程序和相关资源的总称。故即便通过同一份静态程序, 也可以启动多个不同的进程。

1.2 虚拟资源

1.2.1 虚拟CPU

利用进程机制,所有的现代操作系统都支持在同一个时间段来完成多个任务。尽管某个时刻,真实的CPU只能运行一个进程,但是从进程自己的角度来看,它会认为自己在独享CPU(即虚拟CPU),而从用户的角度来看多个进程在一段时间内是同时执行的,即并发执行。在实际的实现中,操作系统会使用调度器来分配CPU资源。调度器会根据策略和优先级来给各个进程一定的时间来占用CPU,进程占用CPU时间的基本单位称为时间片,当进程不应该使用CPU资源时,调度器会抢占CPU的控制权,然后快速地切换CPU的使用进程。这种切换对于用户程序的设计毫无影响,可以认为是透明的。由于切换进程消耗的时间和每个进程实际执行的时间片是在非常小的,以至于用户无法分辨,所以在用户看起来,多个进程是在同时运行的。

ps:了解: 理论上的一些常见通用调度算法

ps:闲聊: 多核心CPU

ps:了解: 查看CPU相关信息

1.2.2 虚拟内存

和虚拟CPU一样, 每个进程不仅从逻辑上独占CPU, 每个进程还从逻辑上还独占一片里连续的内存空间(当然是虚拟的), 我们称为为虚拟内存

虚拟内存通过虚拟映射的方式(按照某种关系把虚拟地址和物理地址相互映射), 从逻辑上为每个进程提供了一个地址连续的、独立使用的内存空间, 这解决了计算实际内存具有大小局限性的问题(如果虚拟地址经过转化之后在真实内存中未命中, 则需要空闲页框加载磁盘数据, 或者当前内存已满/无空闲页框, 则进行页的置换, 这样, 借用硬盘空间作为额外的容器, 以及内存的置换能力,打破了真实内存的限制, 拓展了逻辑上内存的大小); 于此同时虚拟内存机制为每个进程提供了一个独立的地址空间(虚拟的),从而逻辑上隔离了不同进程的内存空间。这样,进程间的内存隔离和解耦,增加了操作系统的稳定性和安全性。

ps: 拓展仅了解: 虚拟内存的大小

ps: 拓展仅了解: 怎么通过虚拟内存访问实际物理空间

2 Linux进程

2.1 进程调度

2.1.1 进程状态

在了解进程调度机制之前, 先谈进程最基本的三种的状态是执行态就绪态阻塞态

  • 执行态:该进程正在运行,即进程正在占用CPU。

  • 就绪态:进程已经具备执行的一切条件,正在等待分配CPU的处理时间片。

  • 等待态:进程不能使用CPU,通常由于等待IO操作、信号量或者其他操作。

ps: 除了最基本的上面3种状态以外,使用ps命令还可以观察到一些更细致划分的状态

2.1.2 Linux进程调度

Linux内核存在一个专门用来调度进程的内核组件,称为调度器, 调度器的基本工作是从一组处于可执行状态的进程中选择一个来执行。

和很多其他操作系统一样, Linux提供了抢占式的多任务模式。调度器会决定某个进程在什么时候停止运行,并且可以让另一个进程进入执行,我们称这个进程在CPU上替换执行的行为抢占

一些常见发生抢占CPU的情况:

  • 时间片用尽:一个进程的时间片用完后 (使用时间片的调度策略),调度器会选择另一个进程运行。

  • 更高优先级的进程变为可运行状态:如果一个更高优先级的进程(特别是实时进程)变为可运行状态,它可以抢占当前较低优先级的运行进程。

  • 中断和系统调用:在处理中断或完成系统调用后,内核可能会重新评估当前运行的进程,并决定是否进行上下文切换。

时间片轮转法

以传统的Unix操作系统为例, 其常采用的时间片轮转法。这种方法的大致流程是这样的:

  • 调度器它把所有的可运行的进程组织在一起,这个数据结构被称为就绪队列。

  • 通常调度器取一个固定时间作为调度周期(又称为调度延迟) ,当一个调度周期开始的时候,调度器会为就绪队列当中的每个进程分配时间片,每个进程能够获取的时间片长度和进程的优先级有关。

  • 正在执行的进程会在执行的过程逐渐消耗它的时间片,当时间片耗尽时,如果该进程依然处于运行状态,那么它就会被就绪队列中的下一个进程抢占, 并且被调度器重新放入就绪队列中, 并根据优先级分配新的时间片, 等待下次执行。

  • 同时, 如果在进程执行过程中,会出现等待IO操作的情况(即使分配的时间片未用尽); 此时,进程就会被放入等待/阻塞队列,并且将自己的状态调整为等待/阻塞状态, 当进程被从等待状态唤醒时,调度器会根据优先级分配时间片,再将其插入到就绪队列当中。

  • 而当一个进程运行终止时,进程也会从就绪队列中移除。

Linux的调度策略

而Linux操作系统采用了多种进程调度策略,用以适应不同的使用场景和需求,而每个进程都有一个关联的调度策略调度优先级(这个调度策略在task_struct中, 并且可以被修改)。主要的进程调度策略包括:

  • 完全公平调度器/Completely Fair Scheduler/CFS/又称普通调度策略/SCHED_OTHER: Linux默认的调度策略, 适用于操作系统中的大部分无特殊要求的任务 (Eg: 如用户界面、文档编辑、互联网浏览等、不需要严格时间约束的后台任务和系统服务...)

  • 实时调度策略/Real-time Scheduling Policies: 又分为先到先服务/FIFO,轮转调度/Round Robin/RR, 适用于对响应时间有严格要求和约束、需要快速响应的任务 (Eg: 硬件控制、实时数据处理... )

  • ... ( man sched )

ps1: 在Linux中, 实时调度策略的进程通常有比CFS/完全公平策略下的进程更高的执行优先级。

ps2: CFS/完全公平调度算法

ps3: 实时调度策略

ps4: 进程默认的优先级别

ps5: nice值调整

ps6: 一些命令: 了解

2.2 进程内存结构

2.2.1 进程模式

在操作系统中,进程通常分为用户态/用户模式进程内核态/内核模式进程

用户模式:

  • 当进程运行在用户模式下时,它执行的是应用程序代码。

  • 在用户模式下,进程没有直接访问硬件资源的权限,也不能直接执行任何可能会影响系统整体稳定性的操作。如果需要进行这些操作(如读写文件、请求更多内存等),进程必须通过系统调用/System call向操作系统请求服务,此时它会从用户模式切换到内核模式。

内核模式:

  • 当进程运行在内核模式下时,它执行的是操作系统内核的代码。

  • 在内核模式下,进程有权限执行任何CPU指令和访问系统的任何内存地址。这是因为它现在是操作系统的一部分,正在执行系统任务(比如处理系统调用或管理硬件设备)。

  • 一旦内核模式下的操作完成,进程会切换回用户模式,继续执行用户级的应用代码。

同一个进程在其生命周期中可能会在用户模式和内核模式之间切换多次。这通常发生在进程进行系统调用时, 需要操作系统介入处理。这个机制确保了操作系统的安全性和稳定性,因为用户应用不能直接执行可能会影响系统整体运行的操作。同时,它也保证了用户应用的运行环境相对简单和安全,因为这些应用无法直接访问敏感的资源或执行关键的系统操作。

2.2.2 用户态空间和内核态空间

当一个新进程被创建时,操作系统会为其分配用户态空间和内核态空间

  • 操作系统使用内存保护机制来确保进程只能访问自己的用户态空间,防止一个进程访问或修改另一个进程的数据,或直接访问内核空间。

  • 在内核态,进程拥有执行低级(底层OR硬件操作)操作的权限。操作系统确保进程只在需要时进入内核态,并在完成后返回用户态,以防止潜在的安全风险。

2.2.3 PCB块:了解

进程控制块/PCB/Process Control Block, 是操作系统中用于存储有关进程信息的数据结构。每个进程在操作系统中都有一个对应的PCB,它包含了操作系统需要管理和调度该进程所必需的所有信息。PCB是操作系统进行进程管理的关键,它使得进程可以被挂起(暂停执行),以后再恢复(继续执行),而且确保进程的执行状态可以被正确地保存和恢复。

PCB通常包含以下类型的信息: (有兴趣记一记, 了解)

PCB的具体内容和结构可能因不同的操作系统而异,但其核心功能是为了使操作系统能够有效地管理和调度进程。在进程创建时,操作系统会为其分配一个PCB,并在进程生命周期中维护这个PCB。在进程结束时,其PCB会被回收利用。通过PCB,操作系统可以保持跟踪所有进程的状态,并在需要时进行上下文切换,确保系统的协调运行。

2.2.4 task_struct

在Linux操作系统中,task_struct是一个结构体,它实现了进程控制块(PCB)的功能,是Linux内核中关于PCB的具体实现(在其他操作系统中,进程的信息结构可能不叫task_struct可能以不同的形式和名称存在,但概念以及其核心目的和功能是一致的:保存和管理操作系统需要的进程信息)。task_struct包含了Linux需要维护的关于进程的几乎所有信息,如进程状态、程序计数器、CPU寄存器、优先级、信号、内存地址等。这样,task_struct使得Linux内核能够跟踪所有进程的状态,并在进行进程调度时进行必要的上下文切换。

仅在此举例: 不用做该操作

task_struct结构体的定义位于Linux内核源代码中。

2.3 进程信息

2.3.1 ps命令

我们可以通过ps命令在Linux系统中用于显示系统中所有进程的信息。同时ps命令的选项很多、功能很复杂(参考 man ps),不过工作中使用最多的是两种:ps -elfps aux

UNIX System V 引入了: ps -elf ( -l: 以长格式显示; -f: 完整格式; -e:列出所有进程 )

UNIX BSD 引入了: ps -aux ( a: 显示所有进程; u:以用户为中心的格式显示进程信息; x:显示没有控制终端的进程 )

2.3.2 free命令

使用free命令也可以查看系统的内存占用信息

2.3.3 top命令

top命令用以动态显示显示系统运行进程的信息。

2.3.4 PID

为了方便普通用户定位每个进程,操作系统为每个进程分配了一个唯一的正整数标识符,称为进程IDPID) 。在Linux中,进程之间存在着亲缘关系,如果一个进程在执行过程中启动了另外一个进程,那么启动者就是父进程,被启动者就是子进程。(实际上PID和PCB之间存在着一一对应关系; 甚至可以从task_struct中看到一个进程的PID和其父进程ID)

  • ps -l命令结果为例,这里会出现两个进程,一个进程是shell,而另一个进程ps。通过PPID可知,ps的父进程 就是bash。

  • 使用系统调用getpid()getppid()可以获取当前运行进程的进程ID和父进程ID。

ps: 拓展: 仅了解

2.3.5 用户组和权限

进程的用户ID和组ID

进程在运行过程中必须具有用户身份,以便于内核进行进程的权限控制。在默认情况下,程序进程拥有启动用户的身份。

  • 假设当前登录用户为A,他运行了任意一个程序, 无论是不是他创建的,则程序在运行过程中就具有A的身份,该进程的用户ID和组ID分别为A和A所属的组,其ID和组ID就被称为进程的真实用户ID真实组ID

  • 真实用户ID和真实组ID可以通过函数getuid()和getgid()获得。

进程的有效用户ID和组ID

进程除了真实用户ID真实组ID以外, 进程还具有有效用户ID有效组ID的概念。

  • 当系统内核对进程的访问权限检查时,它检查的是进程的有效用户ID和有效组ID,而不是真实用户ID和真实组ID

  • 默认情况 下,用户的有效用户ID和真实用户ID是相同的,有效组ID和真实组ID是相同的。

  • 有效用户ID和 有效组ID通过函数geteuid()和getegid()获得。

ps: 拓展

1, 以sudo命令为例, 它的主要作用之一就是通过提升命令的有效用户ID, 来实现执行权限的提升。

2, 有效组ID是可以通过系统调用修改的。(存在安全隐患)

2.3.6 文件权限:拓展

Linux文件权限中,除了基本的读/r、写/w、执行/x权限之外,还有几个特殊的权限位:SetUID、SetGID以及Sticky位。

SetUID

  • SetUID权限使得该文件以文件所有者的身份运行,而不是执行文件的用户的身份。这是通过在文件的权限表示中为所有者的执行位添加一个's'来实现的。例如,如果一个可执行文件的权限是-rwsr-xr-x,那么不论谁运行这个文件,该文件都会以文件所有者的权限运行, 它的有效用户ID也会变为和文件所有者一致的用户身份。

SetGID

与SetUID类似,SetGID权限应用于文件时,使得文件以其所属组的权限运行。在文件权限表示中,这通过在组的执行位添加一个's'来实现,例如-rwxr-sr-x

ps: 拓展

2.3.7 KILL:重要

kill命令

kill命令可以用来给指定的进程发送信号。

  • 通常用户经常会从终端启动shell再启动进程,当进程正在运行时,它可以接受一些键盘发送的信号:比如ctrl+c表示终止信号, ctrl+z表示暂停信号。这种可以直接接受键盘信号的状态被称为前台,否则称为后台。

  • 当进程处于后台的时候,只能通过kill命令发送信号 给它。

  • 使用shell启动进程的时候,如果在末尾加上&符号可以用来直接运行后台进程。

  • 使用ctrl+z可以暂停当前运行的前台进程,并将其放入后台。它也会输出一个任务编号到 屏幕上。

  • ps: (仅拓展知道) 使用jobs命令可以查看和管理所有的后台任务,使用fg命令可以将后台进程拿到前台来。 使用bg命令可以将后台暂停的程序运行起来。

3 Linux进程操作

3.1 创建进程

3.1.1 system

system函数用以执行shell命令, 可以通过system函数传入指定的命令创建一个新进程。 (man system)

EgCode:

  • 由于system函数创建进程的时间消耗是很大的,对于性能要求比较苛刻的任务来说,这种使用 system 的方式往往是不能被接受的。

  • system函数用以执行shell命令, 所以system函数还可以通过shell去调用其他编程语言程序。

3.1.2 fork: 非常重要

fork函数

fork函数用于拷贝当前进程, 以创建一个新进程。 (man fork)

EgCode:

内存复制

当调用fork函数,fork函数会创建一个和当前进程拥有着几乎一致的用户态地址空间, 作为新进程的地址空间。这意味着父进程在 fork函数调用时的内存布局(进程上下文、变量、堆栈、程序代码、等等等等)会被复制到子进程中。

尽管子进程是父进程的副本,但某些属性和资源是根据定义或需要在子进程中进行修改的(主要是PCB),以确保两个进程是独立的。

  • 进程标识符(PID):子进程获得一个唯一的 PID,与父进程不同。

  • 父进程标识符(PPID):子进程的 PPID 设置为创建它的父进程的 PID。

  • ...

写时复制

为了减少数据复制的开销, 优化内存管理, fork采用是写时复制(Copy-On-Write,简称COW)的策略。

  • 在fork()执行时,操作系统并不立即复制父进程的整个内存空间给子进程。操作系统使父进程和子进程暂时共享相同的物理内存页。

  • 这些共享的页面在内存中被标记为只读。如果父进程或子进程尝试写入这些共享的页面(以页为单位),操作系统会为发起写操作的进程(父进程或子进程)分配一个新的物理内存页, 并复制数据到这个页。

写时复制机制确保只有在必要时才复制数据页,这极大地减少了内存使用和提高了效率。

文件打开和fork

当使用fork()函数创建子进程之前我们打开了一个文件,fork时候子进程会拷贝父进程的文件描述符数组,文件描述符数组中存储的指向一个文件表项的指针。但是需要注意的是文件表项是操作系统维护的、可以多个进程程共享。这意味着父进程和子进程将共享打开的文件表项。

而在这个文件表项中, 不仅维护了指向磁盘文件的inode指针, 还维护有文件读写位置的偏移量。这就意味着, 父子进程对同一个文件对象会共享读写位置。

EgCode:

当使用fork()函数创建子进程之后, 父进程和子进程分别打开了一个文件,操作系统维护会创建两个不同的文件表项, 各自独立维护偏移量(即使底层的文件系统对象/inode是共享的)。

这就意味着, 父子进程对同一个文件不会共享读写位置。

EgCode:

3.1.3 exec函数

exec是一系列的系统调用。它们通常适用于在fork之后,用于在当前进程的上下文中加载并运行一个新的程序。

  • 当进程执行到exec系统调用的时候,它会将传入的指令来取代进程本身的代码段、 数据段、栈和堆,然后将PC指针重置为新的代码段的入口。 但进程的 PID 保持不变。

  • exec 当中包括多个不同的函数,这些函数之间只是在传入参数上面有少许的区别。(man execl)

EgCode:

ps:了解: 实际上,system函数以及从bash或者是其他shell启动进程的本质就是fork+exec

3.2 退出进程

在进程阶段,进程总共有5种终止方式,其中3种是正常终止,还有2种是异常终止:

终止方式终止情况
main函数中调用return正常
调用exit函数正常
调用_Exit函数或者_exit函数正常
调用abort函数异常
接收到引起进程终止的信号异常

3.2.1 exit

_Exit_exitexit 都是用于立即终止进程的函数,但它们在处理进程终止时的行为上有所不同:

  • 当调用 _exit_Exit的时候,进程会直接终止返回内核,

  • 而exit函数则会首先执行终止处理程序,然后清理标准IO (就是把所有打开的流执行一次fclose ),最后再终止进程回到内核。

EgCode:

3.2.2 信号退出

当进程处于前台的时候,按下ctrl+c或者是ctrl+\可以给整个进程组发送键盘中断信号 SIGINT和SIGQUIT。

3.2.3 abort: 了解

abort() 函数是一个标准库函数,用于异常终止当前进程。(abort 实际上是通过发送中止信号给当前进程来实现的。。)

3.3 进程控制

3.3.1 孤儿进程

如果父进程先于子进程退出,则子进程成为孤儿进程,此时将自动被PID为1的进程收养, PID为1的进程就成为了这个进程的父进程。当一个孤儿进程退出以后,它的资源清理会交给它的父进程来处理。

EgCode: 配合ps -elf查看

3.3.2 僵尸进程

如果子进程先退出,系统不会自动清理掉子进程的环境,而必须由父进程调用wait或waitpid函数来完成清理工作,如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸进程过多,将会影响系统的性能, 所以必须对僵尸进程进行处理。

(当一个进程执行结束时,它会向它的父进程发送一个SIGCHLD/终止信号,从而父进程可以根据子进程的终止情况进行处理。在父进程处理之前,内核必须要在进程队列当中维持已经终止的子进程的PCB。如果僵尸进程过多,将会占据过多的内核态空间。并且僵尸进程的状态无法转换成其他任何进程状态。)

EgCode: 配合ps -elf查看

3.3.3 wait

wait函数的作用是等待一个已经退出的子进程,并进行清理回收子进程资源工作。

  • wait 随机地等待一个已经退出的子进程,并返回该子进程的PID, 并给子进程进程资源清理和回收

  • wait是一个阻塞函数, wait会一直阻塞直到等到一个结束的子进程, 解除阻塞.(前提是有子进程)(没有子进程的时候, 直接返回-1)

wstatus: 了解

  • 该参数是一个整型指针, 用来存储进程的退出状态。

  • 如果不关心进程的退出状态,那该参数可以是一个NULL。

  • 这个wstatus的整型内存区域中由两部分组成,其中一些位用来表示退出状态(当正常退出时),而另外一些位用来指示发生异常时的信号编号。

  • 我们可以通过一些宏检查状态的情况。 (参考: man 2 wait)

说明
WIFEXITED(wstatus)子进程正常退出的时候返回真,此时可以使用WEXITSTATUS(wstatus),获取子进程的返回情况
WIFSIGNALED(wstatus)子进程异常退出的时候返回真,此时可以使用 WTERMSIG(wstatus)获取信号编号,可以使用 WCOREDUMP(wstatus)获取是否产生core文件
WIFSTOPPED(wstatus)子进程暂停的时候返回真,此时可以使用WSTOPSIG(wstatus)获取信号编号
... 

EgCode:

3.3.4 waitpid

waitpid函数的作用是等待一个已经退出的子进程,并进行清理工作。

  • waitpid 随机地等待一个已经退出的子进程,并返回该子进程的PID

  • waitpid 是一个阻塞函数

pid参数:

  • pid参数可以控制支持更多种模式的等待方式。

PID数值效果
< -1等待进程PID和pid绝对值的进程
== -1等待任一个子进程, 等价于wait
== 0等待同一进程组的任意子进程
> 0等待指定PID的进程

options参数:

  • waitpid是阻塞函数,如果给waitpid 的options参数设置一个名为WNOHANG的宏,则系统调用会变成非阻塞模式。

  • 如果默认阻塞: 填0。

EgCode:

3.4 守护进程

3.4.1 进程组

进程组是一个或多个进程的集合:

  • 每个进程除了是一个单独的进程,还归属于某一个进程组; 所以进程不仅有进程ID, 还有进程组ID

  • 当使用shell创建进程的时候,除了这个进程被创建,这个进程将会创建一个进程组, 并作为进程组的组长。

  • 组长的PID就是进程组ID。 10000, 10000

  • 通过fork创建一个子进程时,子进程默认和父进程属于同一个进程组。 10001 10000 10000

  • 只要进程组当中存在至少一个进程(这个进程即使不是组长),该进程组就存在。

  • getpgrp()函数获得进程组ID。

EgCode:

创建进程组

setpgid(pid, pgid)函数, 允许一个进程改变自己或其他进程的进程组。 (man 2 setpgid)

EgCode:

5.4.2 终端

只是一个名词性的概念.

终端是登录到Linux操作系统所需要的入口设备。终端可以是本地的,也可以是远程的。当操作系统启动的时候, init进程会创建子进程并使用exec来执行getty程序(管理虚拟控制台),从而打开终端设备或者等待远程登录,然后再使用exec调用login程序验证用户名和密码。

5.4.3 会话

会话特点

会话是指一个终端与操作系统之间的一系列交互操作或通信过程。每当用户打开一个新的终端窗口,就会创建一个新的会话。这个会话可以使用户完成从登录到注销的整个过程; 。在此期间,用户与系统的交互(如打开文件、运行程序、等等)都是会话的一部分。

  • 在一个会话中, 可以存在多个进程组。(会话也是一个或者多个进程组的集合)

  • 和控制终端建立连接的会话首进程被称为控制进程。

  • 一个会话存在最多一个前台进程组多个后台进程组

  • 从终端输入的中断,会将信号发送到前台进程组所有进程。

  • 终端断开连接,挂断信号会发送给控制进程, 随着控制进程结束, 正常情况下前台进程组和后台进程组也都会结束。

会话ID

每个会话都有会话ID, 基本上会话ID都是最初创建会话的进程的PID。 (man setsid)(man getsid)

  • 可以使用getsid函数可以获取进程的会话ID。

  • 一个进程可以使用系统调用setsid新建一个会话。 该进程成为新会话的第一个进程/会话领导。

  • 注意: 用来调用setsid创建会话的进程, 不能是进程组组长。

  • 创建新会话以后,即使原来的shell被关闭了,子进程创建的新会话依然不受影响/不会关闭。

EgCode:

5.4.4 守护进程

守护进程Linux操作系统中运行在后台的一种特殊进程,用于执行特定的系统或应用程序任务。与用户交互式进程不同的是它与终端无关,经常长时间持续性运行(直到系统关闭),而非与用户会话相绑定。(Eg: 日志记录、系统监控、调度任务、邮件服务、服务器程序....)

守护进程的创建流程

  • 父进程创建子进程,然后让父进程终止。

  • 在子进程当中创建新会话。

  • 修改当前工作目录为根目录。(因为如果使用当前目录, 意味着当前目录活跃, 则当前目录无法在文件系统中卸载; 而根目录所在的文件系统正常来讲是一直挂在的)

  • 重设文件权限掩码为0,避免创建文件的权限受限。

  • 关闭不需要的文件描述符,比如0、1、2。

EgCode:

5.4.5 日志

使用守护进程经常可以用记录日志, syslog函数 在Linux中可以用于发送消息到系统日志。(操作系统的日志文件存储在/var/log/...中) (man 3 syslog)

EgCode: