嘉兴市网站建设_网站建设公司_展示型网站_seo优化
2025/12/29 1:18:49 网站建设 项目流程

1 前台进程与后台进程

一、基本概念

前台进程

  • 定义:占用当前终端,用户可以直接与其交互
  • 特点
    • 命令行被"锁定",直到进程结束
    • 接收终端输入(stdin)
    • 输出显示在终端(stdout/stderr)
    • 可用 Ctrl+C终止
    • 可用 Ctrl+Z 挂起

后台进程

  • 定义:不占用终端,在后台运行
  • 特点
    • 用户可继续使用终端执行其他命令
    • 无法接收终端输入(除非重定向)
    • 输出仍可能显示在终端(可能干扰当前工作)
    • 终端关闭时可能被终止(除非处理)

启动进程

# 前台启动(默认)./test # 后台启动(末尾加&./test&# 输出:[1]12345#[作业号]进程ID

2 进程组

1-1 什么是进程组

之前我们提到了进程的概念, 其实每一个进程除了有一个进程ID(PID)之外还属于一个进程组。进程组是一个或者多个进程的集合, 一个进程组可以包含多个进程。每一个进程组也有一个唯一的进程组ID(PGID), 并且这个PGID 类似于进程ID, 同样是一个正整数, 可以存放在pid_t 数据类型中。

进程组用于组织相关进程并进行统一管理

进程组(Process Group)要点

  • 一组相关进程的集合
  • 每个进程都属于一个进程组
  • 进程组ID(PGID)等于组长的PID
  • 主要目的是为了作业控制(Job Control)
$ ps-eo pid,pgid,ppid,comm|grep test #结果如下 PID PGID PPID COMMAND283028302259test #-e 选项表示every 的意思, 表示输出每一个进程信息 #-o 选项以逗号操作符(,)作为定界符,可以指定要输出的列

1-2 组长进程

每一个进程组都有一个组长进程。组长进程的ID 等于其进程ID。我们可以通过ps 命令看到组长进程的现象:

从结果上看ps 进程的PID 和PGID 相同, 那也就是说明ps 进程是该进程组的组长进程, 该进程组包括ps 和cat 两个进程。

进程组组长

  • 创建进程组的进程自动成为组长 组长的作用: 进程组组长可以创建一个进程组或者创建该组中的进程
  • 进程组的生命周期: 从进程组创建开始到其中最后一个进程离开为止。组长退出后,进程组仍然存在(成为"孤儿进程组")注意:只要某个进程组中有一个进程存在, 则该进程组就存在, 这与其组长进程是否已经终止无关。

2 会话

2-1 什么是会话

刚刚我们谈到了进程组的概念, 那么会话又是什么呢? 会话其实和进程组息息相关,可以看成是一个或多个进程组的集合, 一个会话可以包含多个进程组。每一个会话也有一个会话ID(SID)

通常我们都是使用管道将几个进程编成一个进程组。如上图的进程组2 和进程组3 可能是由下列命令形成的:

[node@localhost code]$ proc2|proc3&[node@localhost code]$ proc4|proc5|proc6&#&表示将进程组放在后台执行

我们举一个例子观察一下这个现象:

用管道和sleep 组成一个进程组放在后台运行,查看ps 命令打出来的列描述信息

# 过滤sleep 相关的进程信息[node@localhost code]$ ps axj|grep sleep|grep-v grep#a选项表示不仅列当前⽤户的进程,也列出所有其他⽤户的进程#x选项表示不仅列有控制终端的进程,也列出所有⽆控制终端的进程#j选项表示列出与作业控制相关的信息, 作业控制后续会讲#grep-v 选项表示反向过滤, 即不过滤带有grep 字段相关的进程

从上述结果来看3 个进程对应的PGID 相同, 即属于同一个进程组

2-2 如何创建会话

setsid()用于创建一个新的会话(session)并设置进程组。

#include<unistd.h>pid_tsetsid(void);

返回值

  • 成功:返回新的会话 ID(与调用进程的 PID 相同)
  • 失败:返回 -1 并设置 errno

主要作用:

  1. 创建新会话:调用进程成为新会话的领头进程(session leader)
  2. 创建新进程组:调用进程成为新进程组的组长(process group leader)
  3. 脱离控制终端:新会话没有控制终端

具体变化:

  • 进程成为会话首进程:进程 ID成为会话 ID此时, 新会话中只有唯一的一个进程
  • 进程成为进程组组长:进程 ID 成为进程组 ID(PGID)
  • 断开控制终端:该进程没有控制终端。如果在调用setsid 之前该进程存在控制终端, 则调用之后会切断联系

需要注意的是:这个接口如果调用进程原来是进程组组长, 则会报错, 为了避免这种情况, 我们通常的使用方法是先调用fork 创建子进程, 父进程终止, 子进程继续执行, 因为子进程会继承父进程的进程组ID, 而进程ID 则是新分配的, 就不会出现错误的情况。

2-3 会话ID(SID)

上边我们提到了会话ID, 那么会话ID 是什么呢? 我们可以先说一下会话首进程, 会话首进程是具有唯一进程ID 的单个进程, 那么我们可以将会话首进程的进程ID 当做是会话ID。注意:会话ID 在有些地方也被称为会话首进程的进程组ID, 因为会话首进程总是一个进程组的组长进程, 所以两者是等价的。

3 作业控制

3-1 什么是作业(job)和作业控制(Job Control)?

作业是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间互相协作完成任务, 通常是一个进程管道。

Shell 分前后台来控制的不是进程而是作业或者进程组。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell 可以同时运⾏一个前台作业和任意多个后台作业,这称为作业控制

作业控制是Shell的一项功能,允许用户管理多个进程组(作业)在前台和后台之间的切换。

  • 作业 = 一个命令或一组通过管道连接的命令
  • 前台作业:正在与终端交互的进程
  • 后台作业:在后台运行,不与终端交互

例如下列命令就是一个作业,它包括两个命令,在执⾏时Shell 将在前台启动由两个进程组成的作业:

[node@localhost code]$ cat/etc/filesystems|head-n5运⾏结果如下所示: xfs ext4 ext3 ext2 nodev proc

3-2 作业号

放在后台执⾏的程序或命令称为后台命令,可以在命令的后面加上&符号从而让Shell 识别这是一个后台命令,后台命令不用等待该命令执⾏完成,就可立即接收新的命令,另外后台进程执行完后会返回一个作业号以及一个进程号(PID)。

例如下面的命令在后台启动了一个作业, 该作业由两个进程组成, 两个进程都在后台运⾏:

[node@localhost code]$ cat/etc/filesystems|grep ext&

执⾏结果如下:

[1]2202ext4 ext3 ext2 # 按下回车[1]+完成 cat/etc/filesystems|grep--color=autoext

○第一⾏表示作业号和进程ID, 可以看到作业号是1, 进程ID 是2202

○第2-4 ⾏表示该程序运⾏的结果, 过滤/etc/filesystems 有关ext 的内容

○第6 号分别表示作业号、默认作业、作业状态以及所执⾏的命令

关于默认作业:对于一个用户来说,只能有一个默认作业(+),同时也只能有一个即将成为默认作业的作业(-),当默认作业退出后,该作业会成为默认作业。

  • : 表示该作业号是默认作业

-:表示该作业即将成为默认作业

无符号: 表示其他作业

3-3 作业状态

常见的作业状态如下表所示:

3-4 常用作业控制命令

1. 将作业放到后台运行

# 启动时直接放到后台 command&# 例如 sleep100&

2. 暂停当前作业(作业挂起)

Ctrl+Z # 暂停当前前台作业

我们在执⾏某个作业时,可以通过Ctrl+Z 键将该作业挂起,然后Shell 会显示相关的作业号、状态以及所执⾏的命令信息。

例如我们运⾏一个死循环的程序, 通过Ctrl+Z 将该作业挂起, 观察一下对应的作业状态:

#include<iostream>#include<unistd.h>usingnamespacestd;intmain(){while(1){cout<<"hello"<<endl;sleep(1);}return0;}

结果依次对应:作业号 默认作业 作业状态 运行程序信息

3. 作业切换

fg%job_id # 将后台作业切换到前台 bg%job_id # 将暂停的作业在后台继续运行

如果想将挂起的作业切回,可以通过fg 命令,fg 后面可以跟作业号或作业的命令名称。如果参数缺省则会默认将作业号为1 的作业切到前台来执⾏,若当前系统只有一个作业在后台进⾏,则可以直接使用fg 命令不带参数直接切回。具体的参数参考如下:

标识符含义
%N作业号N(N为正整数)
%S以字符串S开头的作业
%?S包含字符串S的作业
%%或%+当前作业
%-前一个作业

例如我们把刚刚挂起来的./test 作业切回到前台:

注意: 当通过fg 命令切回作业时,若没有指定作业参数,此时会将默认作业切到前台执行,即带有“+”的作业号的作业

4. 查看当前作业

jobs # 选项 jobs-l # 显示作业详细信息 jobs-p # 只显示PID jobs-r # 只显示运行中的作业 jobs-s # 只显示暂停的作业

例如, 我们先在前台及后台运⾏两个作业, 并将前台作业挂起, 来用jobs 命令查看作业相关的信息:

5. 终止作业

kill%job_id # 向作业发送信号 kill-9%job_id # 强制终止

6. 作业控制相关的信号

上面我们提到了键入Ctrl + Z 可以将前台作业挂起,实际上是将STGTSTP 信号发送至前台进程组作业中的所有进程, 后台进程组中的作业不受影响(因为后台作业接收不到终端的信息)。在unix系统中, 存在3 个特殊字符可以使得终端驱动程序产生信号, 并将信号发送至前台进程组作业, 它们分别是:

Ctrl + C: 中断字符, 会产生SIGINT 信号

Ctrl + \: 退出字符, 会产生SIGQUIT 信号

Ctrl + Z:挂起字符, 会产生STGTSTP 信号

守护进程

什么是守护进程

守护进程是一种在操作系统后台长期运行的特殊进程,通常独立于任何终端或用户会话。它的名字“Daemon”来源于希腊神话中的“守护神”,寓意其默默在后台守护系统或提供服务。

核心特征:

  1. 生命周期长:从系统启动时(或需要时)启动,一直运行到系统关闭。
  2. 无控制终端:它不依赖于用户的登录或终端。即使所有用户都注销,守护进程依然在运行。
  3. 在后台运行:通常不与前台用户进行直接交互(没有图形界面或命令行输入/输出)。
  4. 自成进程组与会话组:这是实现“脱离终端”的关键技术步骤。

4 为什么需要守护进程?

许多系统关键功能和网络服务都需要不间断地运行,例如:

  • 网络服务:Web服务器(如 nginx、httpd)、数据库服务器(如 mysqld)、SSH服务器(如 sshd)
  • 系统服务:计划任务调度(如 crond)、日志管理(如 rsylogd)、打印服务(如 cupsd)。
  • 硬件监控:电源管理、温度监控等。

这些任务不适合由用户手动在前台启动和维持,因此需要交给守护进程。

4-1 传统创建守护进程的步骤(编程视角)

在Linux/Unix系统中,一个程序要成为守护进程,通常需要经过以下“脱胎换骨”的步骤(以C语言为例):

  1. fork() 并退出父进程
    • 程序首先调用 fork() 创建一个子进程。
    • 然后父进程立即退出。这样做的目的是让子进程成为“孤儿进程”,并被系统的 init进程(或 systemd)接管,同时向Shell返回一个“命令执行完毕”的假象。
  2. setsid()创建新会话
    • 子进程调用setsid()创建一个全新的会话,并成为该会话的首领进程
    • 这一步是核心:它使进程脱离原来的控制终端、进程组和会话,获得完全的独立性。
  3. 再次 fork()(可选但推荐)
    • 再次fork()并让父进程(即刚才的会话首领)退出。
    • 这样,新创建的子进程就不再是会话首领,根据系统规则,它永远无法再申请获得控制终端,更加安全。
  4. 改变工作目录
    • 使用chdir(“/”)将进程的当前工作目录改为根目录。这是为了防止守护进程占用某个可卸载的文件系统(如U盘目录),导致其无法被卸载。
  5. 重设文件权限掩码
    • 使用umask(0)。这给了守护进程最大的文件操作自由度,使其可以创建任何权限的文件。具体的权限应由守护进程自身逻辑控制。
  6. 设置文件描述符
    • 继承自父进程的文件描述符(如标准输入 stdin、标准输出 stdout、标准错误 stderr)已经无用且可能造成资源泄露或意外输入。
    • 遍历并关闭所有打开的文件描述符(不推荐),或将它们重定向到 /dev/null(一个黑洞设备)。

完成这些步骤后,程序就正式成为了一个守护进程,可以在后台执行它的核心任务了。

补充:/dev/null

/dev/null 是一个特殊的设备文件,通常被称为“空设备”“位桶”

  • 写入它的数据会被系统立即丢弃,消失得无影无踪。
  • 从它读取时,会立即返回一个EOF(文件结束符),就像读一个空文件。
主要用途
  1. 丢弃命令的输出(特别是无用或烦人的输出)
    当您运行一个命令,但不想看到它的标准输出或错误信息时,可以将其重定向到 /dev/null
# 丢弃标准输出(stdout) command>/dev/null # 丢弃标准错误(stderr)。注意:2stderr的文件描述符 command2>/dev/null # 丢弃所有输出(stdoutstderr都丢弃) command>/dev/null2>&1# 或者更简洁的现代写法(bash):command&>/dev/null

实用例子

  • 运行一个后台脚本,不想让它的输出干扰终端:
./my_script.sh>/dev/null2>&1&

2.创建空文件
虽然通常用 touch 命令创建空文件,但您也可以用 /dev/null 作为源:

cat/dev/null>empty_file.txt cp/dev/null empty_file.txt

用于测试或满足需要输入文件参数的命令
有些命令必须指定一个输入文件,但您不想处理真实文件时,可以用它:

grep"pattern"/dev/null # 这会让grep只搜索“模式”,但输入源是空的,常用于脚本中安全地测试grep选项。

快速清空一个文件
将 /dev/null 的内容重定向到文件,可以瞬间清空文件内容,而不用删除再创建:

:>/dev/null>my_log.txt # 或者更常见的写法:>my_log.txt
一个经典比喻

把 /dev/null 想象成计算机世界的黑洞。任何东西扔进去(写入)都会永远消失,而从里面也什么都拿不出来(读取时只能得到“空”)

简单例子

#1.看看它的文件类型?它是一个字符设备文件 $ ls-l/dev/null crw-rw-rw-1root root1,3日期/dev/null # 注意开头的'c'表示字符设备,以及它的权限是所有人都可读可写。 #2.试着读它?立即得到结束符。 $ cat/dev/null (没有任何输出) #3.试着写东西给它?成功执行,但内容被丢弃。 $ echo"Hello, this will vanish forever">/dev/null # 命令正常执行,但文字不会出现在任何地方。 #4.丢弃恼人的错误信息 $ find/-name"*.conf"2>/dev/null # 这样会只显示找到的.conf文件,而把“权限拒绝”之类的错误信息全部隐藏。

4-2 常见例子

在终端中输入 ps aux | grep d$ ,你可以看到很多以 d 结尾的进程名(d 即 Daemon 的缩写),它们大多是守护进程:

  • systemd:所有进程的父进程,系统初始化守护进程。
  • sshd:负责处理SSH远程连接的守护进程。
  • nginx 或 apache2:Web服务器守护进程。
  • mysqld 或 postgres:数据库守护进程。
  • cron:定时任务调度守护进程。

4-3 总结

特性描述
本质长期运行在后台、无控制终端的独立进程。
目的提供不间断的系统或网络服务。
传统创建通过 fork、setsid、改变目录/权限、关闭文件描述符等步骤实现。
现代管理通过 systemd的 .service文件进行配置和管理,无需在代码中实现守护进程化逻辑。
关键点独立性(脱离终端和用户会话)和持久性

简单来说,守护进程就是操作系统里默默无闻的“后台工作者”,它们保证了你的服务器、数据库、网络连接等核心服务24小时不间断地运行。

4-4 将服务守护进程化

#pragmaonce#include<iostream>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<signal.h>voidDaemon(){// 1. 忽略可能引起程序异常退出的信号signal(SIGCHLD,SIG_IGN);signal(SIGPIPE,SIG_IGN);// 2. 让进程不要是组长if(fork()>0)exit(0);// 3. 每一个进程都有自己的CWD,建议将当前进程的CWD 更改成为/根目录chdir("/");// 4. 将自己设计成为新的会话,后面的代码其实是子进程在走setsid();// 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了 两种方法// a. 关闭 b. 标准输入,标准输出,标准错误 -> 重定向 -> /dev/null(最佳实践)intfd=open("/dev/null",O_RDWR);if(fd>=0){dup2(fd,0);dup2(fd,1);dup2(fd,2);}}

接下来只要在相应服务的程序中调用Daemon()就可以了

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询