应用编程之进程(二)

一、执行新程序

当子进程的工作不再是运行父进程的代码段,而是运行另一个新程序的代码,那么这个时候子进程可以通过 exec 函数来实现运行另一个新的程序。
1、 execve() 函数
系统调用 execve() 可以将新程序加载到某一进程的内存空间,通过调用 execve() 函数将一个外部的可执 行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main() 函数开始执行。
execve() 的成功调用将永不返回,而且也无需检查它的返回值,实际上,一旦该函数返回,就表明它 发生了错误。
基于系统调用 execve() ,还提供了一系列以 exec 为前缀命名的库函数,虽然函数参数各异,当其功能 相同,通常将这些函数(包括系统调用 execve() )称为 exec 族函数,所以 exec 函数并不是指某一个函数、 而是 exec 族函数,通常将调用这些 exec 函数加载一个外部新程序的过程称为 exec 操 作。
2、exec 库函数
exec 族函数包括多个不同的函数,这些函数命名都以 exec 为前缀,上一小节给大家介绍的 execve() 函数也属于 exec 族函数中的一员,但它属于系统调用;本小节我们介绍 exec 族函数中的库函数,这些库函数 都是基于系统调用 execve() 而实现的,虽然参数各异、但功能相同,包括: execl() execlp() execle() execv() 、execvp()、 execvpe()
3system() 函数
使用 system() 函数可以很方便地在我们的程序当中执行任意 shell 命令。system()函数其内部的是通过调用 fork()、execl()以及 waitpid()这三个函数来实现它的功能,首先 system() 会调用 fork()创建一个子进程来运行 shell(可以把这个子进程成为 shell 进程),并通过 shell 执行参数command 所指定的命令。
system() 的主要优点在于使用上方便简单,编程时无需自己处理对 fork() exec 函数、 waitpid() 以及 exit() 等调用细节,system() 内部会代为处理;当然这些优点通常是以牺牲效率为代价的,使用 system() 运行 shell 命令需要至少创建两个进程,一个进程用于运行 shell 、另外一个或多个进程则用于运行参数 command 中解 析出来的命令,每一个命令都会调用一次 exec 函数来执行;所以从这里可以看出,使用 system() 函数其效 率会大打折扣,如果我们的程序对效率或速度有所要求,那么建议大家不是直接使用 system()

二、进程状态

Linux 系统下进程通常存在 6 种不同的状态,分为:就绪态、运行态、僵尸态、可中断睡眠状态(浅度睡眠)、不可中断睡眠状态(深度睡眠)以及暂停态。
就绪态( Ready ):指该进程满足被 CPU 调度的所有条件但此时并没有被调度执行,只要得到 CPU 就能够直接运行;意味着该进程已经准备好被 CPU 执行,当一个进程的时间片到达,操作系统调度程序会从就绪态链表中调度一个进程;
运行态:指该进程当前正在被 CPU 调度运行,处于就绪态的进程得到 CPU 调度就会进入运行态;
僵尸态:僵尸态进程其实指的就是僵尸进程,指该进程已经结束、但其父进程还未给它“收尸”;
可中断睡眠状态:可中断睡眠也称为浅度睡眠,表示睡的不够“死”,还可以被唤醒,一般来说可 以通过信号来唤醒;
不可中断睡眠状态:不可中断睡眠称为深度睡眠,深度睡眠无法被信号唤醒,只能等待相应的条件 成立才能结束睡眠状态。把浅度睡眠和深度睡眠统称为等待态(或者叫阻塞态),表示进程处于一 种等待状态,等待某种条件成立之后便会进入到就绪态;所以,处于等待态的进程是无法参与进程系统调度的。
暂停态:暂停并不是进程的终止,表示进程暂停运行,一般可通过信号将进程暂停,譬如 SIGSTOP 信号;处于暂停态的进程是可以恢复进入到就绪态的,譬如收到 SIGCONT 信号。
                 

三、进程关系

Linux 系统下,每个进程都有自己唯一的标识:进程 号(进程 ID PID ),也有自己的生命周期,进程都有自己的父进程、而父进程也有父进程,这就形成了一 个以 init 进程为根的进程家族树;当子进程终止时,父进程会得到通知并能取得子进程的退出状态。
除此之外,进程间还存在着其它一些层次关系,譬如进程组和会话;所以,由此可知,进程间存在着多种不同的关系,主要包括:无关系(相互独立)、父子进程关系、进程组以及会话。
父子进程关系
两个进程间构成父子进程关系,譬如一个进程 fork() 创建出了另一个进程,那么这两个进程间就构成了 父子进程关系,调用 fork() 的进程称为父进程、而被 fork() 创建出来的进程称为子进程;当然,如果“生父” 先与子进程结束,那么 init 进程(“养父”)就会成为子进程的父进程,它们之间同样也是父子进程关系。
进程组
每个进程除了有一个进程 ID 、父进程 ID 之外,还有一个进程组 ID ,用于标识该进程属于哪一个进程 组,进程组是一个或多个进程的集合,这些进程并不是孤立的,它们彼此之间或者存在父子、兄弟关系,或者在功能上有联系。
Linux 系统设计进程组实质上是为了方便对进程进行管理。假设为了完成一个任务,需要并发运行 100 个进程,但当处于某种场景时需要终止这 100 个进程,若没有进程组就需要一个一个去终止,这样非常麻烦 且容易出现一些问题;有了进程组的概念之后,就可以将这 100 个进程设置为一个进程组,这些进程共享一个进程组 ID ,这样一来,终止这 100 个进程只需要终止该进程组即可。
关于进程组需要注意以下以下内容:
每个进程必定属于某一个进程组、且只能属于一个进程组;
每一个进程组有一个组长进程,组长进程的 ID 就等于进程组 ID
在组长进程的 ID 前面加上一个负号即是操作进程组
组长进程不能再创建新的进程组;
只要进程组中还存在一个进程,则该进程组就存在,这与其组长进程是否终止无关;
⚫ 一个进程组可以包含一个或多个进程,进程组的生命周期从被创建开始,到其内所有进程终止或离开该进程组;
默认情况下,新创建的进程会继承父进程的进程组 ID
会话
会话是一个或多个进程组的集合,其与进程组、进程之间的关系如下图所示:
一个会话可包含一个或多个进程组,但只能有一个前台进程组,其它的是后台进程组;每个会话都有一个会话首领(leader),即创建会话的进程。一个会话可以有控制终端、也可没有控制终端,在有控制终端的情况下也只能连接一个控制终端,这通常是登录到其上的终端设备(在终端登录情况下)或伪终端设备(譬如通过 SSH 协议网络登录),一个会话中的进程组可被分为一个前台进程组以及一个或多个后台进程组。
会话的首领进程连接一个终端之后,该终端就成为会话的控制终端,与控制终端建立连接的会话首领进 程被称为控制进程;产生在终端上的输入和信号将发送给会话的前台进程组中的所有进程,譬如 Ctrl + C (产 生 SIGINT 信号)、 Ctrl + Z (产生 SIGTSTP 信号)、 Ctrl + \ (产生 SIGQUIT 信号)等等这些由控制终端产生的信号。
当用户在某个终端登录时,一个新的会话就开始了;当我们在 Linux 系统下打开了多个终端窗口时,实际上就是创建了多个终端会话。

四、守护进程

1、守护进程的概念和特点

守护进程( Daemon )也称为精灵进程,是运行在后台的一种特殊进程,它独立于控制终端并且周期性 地执行某种任务或等待处理某些事情的发生,主要表现为以下两个特点:
长期运行。 守护进程是一种生存期很长的一种进程,它们一般在系统启动时开始运行,除非强行终 止,否则直到系统关机都会保持运行。与守护进程相比,普通进程都是在用户登录或运行程序时创 建,在运行结束或用户注销时终止,但守护进程不受用户登录注销的影响,它们将会一直运行着、 直到系统关机。
与控制终端脱离。 Linux 中,系统与用户交互的界面称为终端,每一个从终端开始运行的进程都会依附于这个终端,也就是会话的控制终端。当控制终端被 关闭的时候,该会话就会退出,由控制终端运行的所有进程都会被终止,这使得普通进程都是和运行该进程的终端相绑定的;但守护进程能突破这种限制,它脱离终端并且在后台运行,脱离终端的目的是为了避免进程在运行的过程中的信息在终端显示并且进程也不会被任何终端所产生的信息所打断。
守护进程是一种很有用的进程。 Linux 中大多数服务器就是用守护进程实现的,譬如, Internet 服务器 inetd、 Web 服务器 httpd 等。同时,守护进程完成许多系统任务,譬如作业规划进程 crond 等。 守护进程 Daemon ,通常简称为 d ,一般进程名后面带有 d 就表示它是一个守护进程。守护进程与终端 无任何关联,用户的登录与注销与守护进程无关、不受其影响,守护进程自成进程组、自成会话,即pid=gid=sid。
2、SIGHUP信号
当用户准备退出会话时,系统向该会话发出 SIGHUP 信号,会话将 SIGHUP 信号发送给所有子进程, 子进程接收到 SIGHUP 信号后,便会自动终止,当所有会话中的所有进程都退出时,会话也就终止了;因为程序当中一般不会对 SIGHUP 信号进行处理,所以对应的处理方式为系统默认方式, SIGHUP 信号的系统默认处理方式便是终止进程。
这也就解释了,为什么子进程会随着会话的退出而退出,因为它收到了 SIGHUP 信号。不管是前台进程还是后台进程都会收到该信号。

五、单例模式运行

通常情况下,一个程序可以被多次执行,即程序在还没有结束的情况下,又再次执行该程序,也就是系统中同时存在多个该程序的实例化对象(进程),譬如大家所熟悉的聊天软件 QQ ,我们可以在电脑上同时 登陆多个 QQ 账号,譬如还有一些游戏也是如此,在一台电脑上同时登陆多个游戏账号,只要你电脑不卡机、随便你开几个号。 但对于有些程序设计来说,不允许出现这种情况,程序只能被执行一次,只要该程序没有结束,就无法 再次运行,我们把这种情况称为单例模式运行。譬如系统中守护进程,这些守护进程一般都是服务器进程, 服务器程序只需要运行一次即可,能够在系统整个的运行过程中提供相应的服务支持,多次同时运行并没有意义、甚至还会带来错误!

相关推荐

  1. DPDK系列四十DPDK应用网络编程

    2024-04-20 22:42:06       43 阅读
  2. DPDK系列四十DPDK应用网络编程TCP编程

    2024-04-20 22:42:06       37 阅读
  3. DPDK系列四十DPDK应用网络编程UDP编程

    2024-04-20 22:42:06       28 阅读
  4. 网络编程XDP技术应用

    2024-04-20 22:42:06       25 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-20 22:42:06       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-20 22:42:06       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-20 22:42:06       82 阅读
  4. Python语言-面向对象

    2024-04-20 22:42:06       91 阅读

热门阅读

  1. docker-002常用命令

    2024-04-20 22:42:06       34 阅读
  2. 数据结构6:时间复杂度和空间复杂度

    2024-04-20 22:42:06       143 阅读
  3. vue处理异步状态的逻辑useAsyncState

    2024-04-20 22:42:06       45 阅读
  4. Spring Cloud Gateway面试题

    2024-04-20 22:42:06       173 阅读
  5. Ubuntu如何给tar.gz文件创建桌面快捷方式

    2024-04-20 22:42:06       176 阅读