进程并不是完全孤立的个体,或是父子兄弟,或是功能间的相似
通过进程间关系将进程有组织的管理,才让操作系统更加强大、灵活
本文通过介绍进程组、作业、会话讲解进程间关系的基本概念
并延伸介绍一下守护进程
介绍守护进程前,需要先明确作业、会话的概念
进程组
进程在系统中并不是完全独立的个体,每个进程都隶属于一个进程组
使用ps axj
可以看到相关信息
1 | sleep 100 | sleep 200 | sleep 300 & ps axj | head -n1 & ps axj | grep sleep | grep -v grep |
其中
- ‘&’: 表⽰示将进程组放在后台执⾏行
ps
的参数j
表示显示作业相关信息(下文介绍作业)- 进程组ID为组长进程ID
- 组长退出后进程组还在
作业
进程组方便了我们灵活地控制一组进程的状态
但有时我们不仅需要控制状态,还要控制正在进行进程的行为(比如挂起一进程),称为作业控制
在作业控制的角度看进程,进程就是作业
常用的操作比如
1 | proc1 #表示将proc1作业(进程)放到前台运行 |
由上例易知,shell创建一个进程即位作业,但作业可以不仅仅包含一个进程
1 | ps axj | grep root | more & |
此时就运行了三个进程
有了作业控制,如果用户在编辑一个文本,可以挂起该作业,运行完其它操作再继续编辑
如
1 | vim test.txt #打开文件后按下 ^Z(Ctrl+Z),vim会挂起 |
此时我们可以在这个shell中做其它操作,当想要回到编辑器时
1 | jobs -l #-l表示显示作业相关进程的pid |
此外,一个进程fork出的子进程不属于当前作业,很多时候不严格区分进程组和作业
会话
每打开一个终端都会建立一个会话
建立会话的进程叫控制进程,一个会话包含一个前台作业和若干后台作业
Linux下打开一个终端
1 | [lxk@localhost ~]$ ls | grep D | more & |
再打开一个新的终端
1 | ls | grep D | more & ps axj | head -1 ; ps axj | grep more | grep -v grep PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 11579 11641 11639 11579 pts/1 11758 T 1000 0:00 more 11692 11749 11747 11692 pts/2 11692 T 1000 0:00 more |
可见两个后台的进程属于不同的会话
另外,在mac os下(BSD),不同的终端属于一个会话
守护进程
实际开发我们需要一种进程:
独立于终端之外,不随终端的退出而退出,也不受终端发出的信号的影响,周期地执行某项管理服务
这就是守护进程
我们可以看到很多系统的进程都是守护进程(TPGID = -1)
1 | ps axj | head -n3 |
预定成俗的,守护进程通常以d结尾,表示daemon(精灵)
打印机例子
打印的任务就用到了守护进程
打印机是一种独占设备,若一个进程打开它又长时间不用,就会导致其他进程都无法使用打印机。
通过创建一个守护进程,当一个进程需要打印文件时,将待打印文件放置到指定目录,由守护进程负责打印工作,即解决了进程空占打印机问题
守护进程的创建
1 |
|
其中
- 为了使得守护进程权限最大,需要把文件掩码置0
- 为了使得当前进程脱离当前会话,需要执行setpid
pid_t setsid(void);
- 创建一个新的会话,返回 session id (等于进程id),失败返回-1
- 若当前进程为进程组组长,则执行失败,所以创建守护进程需要先fork一次
- 更改工作路径到根目录(或对应盘符无法卸载或目录被强制卸载、守护进程失效)
此外,在更改工作目录前可以fork第二次,
fork的第二次的原因是在执行setsid后进程变成了会话组长,会话组长有权限再次打开终端
通过再次fork,可以保证守护进程无法再次打开终端
但这并不是必须的(很多开源项目都没有使用)
daemon函数
Linux下实际开发中,我们可以使用daemon函数直接创建守护进程
1 | #include <stdio.h> |
其中
int daemon(int nochdir, int noclose);
- nochdir:=0 将当前目录更改至“/”
- noclose:=0 将标准输入、标准输出、标准错误重定向至“/dev/null”
- /dev/null被称为黑洞,不管哪个文件描述符对其进行写操作,它都会直接将数据丢弃;
- 成功返回0,失败放回-1