任务管理
在运行进程的时候后面加上&
,表示让这个进程放在后台运行。
每一个进程都是一个任务,都有一个作业号
使用jobs
命令可以查看所有的任务和其作业号
使用fg 作业号
可以让后台进程放到前台运行
后台运行的进程的状态是R
,而前台运行的进程状态是R+
在bash
中只允许存在一个前台进程,而bash
也是一个进程,所以当前台已经有一个进程在运行的时候,bash
就不能解析命令了,所以此时尝试运行命令是没有用的。
使用ctrl + z
组合将可以让进程以暂停的状态运行在后台
使用bg 作业号
,可以将后台进程运行起来
使用终端登录Linux
,本质都是先创建一个bash
进程,整体称之为一个会话session
,所有的命令行启动的任务都是在对应session
中运行的
使用ps -o pid,ppid,pgrp,session,tpgid,command
查看当前会话的进程号,进程组号,会话号,终端进程组号,进程名称
进程组
每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。通常,它们与同一个作业相关联,可以接收来自同一个终端的各种信号。
命令行上启动的多个进程,协同完成某项任务,其中第一个被创建出来的进程,称之为组长进程。组长进程的进程号和进程组号是相同的。
作业
shell分前后台控制的不是进程,而是作业或者进程组。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,shell可以运行一个前台作业和多个后台作业。
会话
会话是一个或多个进程组的集合。
一个会话可以有一个控制终端。建立与控制终端连接的会话首进程被称为“控制进程”。一个会话中应该包括控制进程,一个前台进程组合多个后台进程组。
守护进程
当一个会话关闭之后,其中所有的进程会变成孤儿进程。为了在会话关闭之后,还可以是一个进程持久地服务下去,就需要使用到守护进程
守护进程也称为“精灵进程”,是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。Linux的大多数服务器就是用守护进程实现的。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销终止,但系统服务进程不受用户登录注销的影响,一直运行。这些进程就是守护进程。
TPGID为-1的进程就是没有控制终端的进程也就是守护进程,在COMMAND
一列用[]
括起来的表示该进程为内核线程。
系统调用接口daemon
#include <unistd.h>
int daemon(int nochdir, int noclose);
- 作用
- 使得当前进程变成守护进程
- 参数
nochdir
:是否需要更改守护进程的工作目录为根目录,默认是更改noclose
:是否需要关闭守护进程中的文件描述符
模拟实现daemon进程
实现守护进程的核心步骤就是让一个非进程组组长进程创建出一个新的会话,由于当前会话不能干预新创建出的会话,所以当前进程就可以一直运行下去了。
- 调用
umask
将文件屏蔽字设置为0 - 调用
fork
,父进程退出。
- 原因
- 如果父进程是
shell
命令启动的,那么父进程退出shell
认为命令已经执行完毕,此时只剩下一个子进程,该子进程不是进程组的组长,满足使用setsid
的条件。
- 如果父进程是
- 忽略SIGCHLD信号
- (核心步骤)使用
setsid
创建一个新的会话
- 原因
- 使用
setsid
可以创建出一个新的会话,使当前进程成为会话首进程,同时也是进程组的组长。但是使用setsid
的前提是当前进程不能是组长进程,而为了不成为组长进程,需要fork
出一个子进程,子进程可以调用setsid
了
- 使用
- 再
fork
一次,父进程退出
- 原因
- 只有会话首进程才能打开一个终端,因为守护进程不需要再打开终端了,所以可以再
fork
一次,让父进程退出,如此子进程就不是会话首进程,也就不能打开终端了
- 只有会话首进程才能打开一个终端,因为守护进程不需要再打开终端了,所以可以再
- 将当前工作目录更改为根目录
- 原因
- 将当前工作目录设置为根目录,这样守护进程必须要使用绝对路径的方式访问文件。可以使用
ls /proc/[pid] -al
命令查看进程当前工作目录
- 将当前工作目录设置为根目录,这样守护进程必须要使用绝对路径的方式访问文件。可以使用
- 关闭不需要的文件描述符或者将3个标准文件重定向到
/dev/null
中
- 原因
- 让进程和IO设备取消关联,如果将文件重定向到
/dev/null
代表将输入到源文件的文件全丢弃
- 让进程和IO设备取消关联,如果将文件重定向到
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
void my_daemon(int nochdir, int noclose)
{
umask(0);
if (fork() > 0)
{
exit(0);
}
signal(SIGCHLD, SIG_IGN);
setsid();
if (fork() > 0)
{
exit(0);
}
chdir("/");
int fd = open("/dev/null", O_RDONLY);
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
}
int main()
{
my_daemon(0, 0);
return 0;
}