我们工作中接触最多的就是 进程,但是我们对它又比较陌生,因为它是业务不需要关心的地方,既有的公有组件和操作系统已经对我们屏蔽了它的复杂性。然后跟它的接触时间一长,我们难免会对它产生好奇:How it works?
什么是进程
进程是 程序的实体,是系统进行 资源分配和调度的基本单位,是操作系统结构的基础。每个进程都有自己唯一标识(PID),每个进程都有父进程,这些父进程也有父进程,所有进程都是init进程(PID 为 1)的子进程。
我们来直观感受下它的存在,可以说它是看不见又摸不着。
|
进程分类
前台进程
前台进程具有控制终端,会堵塞控制终端。它的特点是:
- 可以同用户交互,但容易被意外终止;
- 有较高的响应速度,优先级别稍高;
|
通常,在控制终端使用Ctrl+C组合键,会导致前台进程终止退出。
守护进程
守护进程是一种运行在后台的特殊进程,因为它不属于任何一个终端,所以不会收到任何终端发来的任何信号。它与前台进程显著的区别是:
- 它没有控制终端,不能直接和用户交互,在后台运行;
- 它不受用户登录和注销的影响,只受开机或关机的影响,可以长期运行;
通常我们编写的程序,都需要在 后台不终止的长期运行 ,此时就可以使用守护进程。当然,我们可以在代码中调用系统函数,或者直接在启动命令后追加&操作符,如下:
|
通常&与 nohup 结合使用,忽略 SIGHUP 信号来实现一个守护进程。该方式对业务代码侵入最小,方便且成本低,常用于临时执行任务脚本的场景。
进程间通信(InterProcess Communication)
进程的用户空间是相互独立的,一般而言是不能相互访问。但很多情况下,进程间需要互相通信来进行数据传输、共享数据、通知事件、进程控制等,这就必须通过内核实现进程间通信。
进程间通信有管道、消息队列、信号、共享内存、套接字等方式,本文只介绍后 3 种。
共享内存(Shared Memory)
共享内存是一段被映射到多个进程地址空间的内存,虽然这段共享内存是由一个进程创建,但是多个进程都可以访问。如下图:
共享内存是最快的进程间通信方式,但是可能会存在竞争,因此需要加锁。Linux 支持三种共享内存:mmap、Posix、以及 System V。
套接字(Socket)
套接字是一个通信链的句柄,可以用域、端口号、协议类型来表示一个套接字,其中域分为 Internet 网络(IP 地址)和 UNIX 文件(Sock 文件)两种。当域为 Internet 网络时,通信流程如下图:
特别的是,当套接字域为 Internet 网络时,可以实现 跨主机的进程间通信。因此,若要实现跨主机进行进程间通信,则须选用套接字。
信号(Signal)
信号受事件驱动,是一种异步且最复杂的通信方式,用于通知接受进程有某个事件已经发生,因此常用于事件处理。信号的处理机制,如下图:
常用的信号值
在 Linux 系统中,可使用kill -l命令查看这 62 个信号值。其中常用值如下:
信号名称 |
值 |
说明 |
进程默认行为 |
SIGHUP |
1 |
终端控制进程结束 |
Terminate |
SIGINT |
2 |
键盘Ctrl+C被按下 |
Terminate |
SIGQUIT |
3 |
键盘Ctrl+/被按下 |
Dump |
SIGKILL |
9 |
无条件结束进程 |
Terminate |
SIGUSR1 |
10 |
用户保留 |
Terminate |
SIGUSR2 |
12 |
用户保留 |
Terminate |
SIGALRM |
14 |
时钟定时信号 |
Terminate |
SIGTERM |
15 |
程序结束 |
Terminate |
SIGCHLD |
16 |
子进程结束 |
Ignore |
产生信号的方式
实际中,硬件或者软件中断都会触发信号,但这里只列举两种信号产生方式。
- 终端按键
按键/命令 |
信号名称 |
Ctrl+C |
SIGINT |
Ctrl+ |
SIGQUIT |
EXIT |
SIGHUP |
- 系统调用
通过kill系统调用发送信号。例如,在 Shell 中使用kill -9发送 SIGKILL 信号。对于kill调用,需要注意以下两种特殊情况:
1、 特殊信号
可以发送编号为0的信号来 检测进程是否存活。
|
2、 特殊 PID
这里的参数$pid,根据取值范围不同,含义也不同。具体如下:
- $pid > 0: 向 PID 为 $pid 的进程发送信号
- $pid = 0:向当前进程组所有进程发送信号,比较常用;
- $pid = -1:向所有进程(除 PID 为 1)发送信号(权限);