【Linux网络编程】进程间关系与守护进程

【Linux网络编程】进程间关系与守护进程 【Linux网络编程】进程间关系与守护进程1. 进程组1.1 什么是进程组1.2 组长进程2. 会话2.1 什么是会话2.2 如何创建会话2.3 会话IDSID3. 控制终端4. 作业控制4.1 什么是作业job和作业控制Job Control4.2 作业号4.3 作业状态4.4 作业的挂起与切回4.4.1 作业挂起4.4.1 作业切回4.5 查看后台执行或挂起的作业4.6 作业控制相关的信号5. 守护进程6. 如何将服务守护进程化1. 进程组1.1 什么是进程组每一个进程除了有一个进程ID(PID)之外还属于一个进程组。进程组是一个或者多个进程的集合一个进程组可以包含多个进程。每一个进程组也有一个唯一的进程组ID(PGID)并且这个PGID类似于进程ID同样是一个正整数可以存放在pid_t数据类型中。$ps-eopid,pgid,ppid,comm|greptest#结果如下PID PGIDPPIDCOMMAND283028302259test# -e 选项表示every的意思 表示输出每一个进程信息# -o 选项以逗号操作符,作为定界符, 可以指定要输出的列1.2 组长进程每一个进程组都有一个组长进程。组长进程的ID等于其进程组ID。我们可以通过ps命令看到组长进程的现象[nodelocalhost code]$ ps-o pid,pgid,ppid,comm|cat# 输出结果PID PGIDPPIDCOMMAND280628062805bash288028802806ps288128802806cat从结果上看ps进程的PID和PGID相同那也就是说明ps进程是该进程组的组长进程该进程组包括ps和cat两个进程。进程组组长的作用进程组组长可以创建一个进程组或者创建该组中的进程进程组的生命周期从进程组创建开始到其中最后一个进程离开为止。注意只要某个进程组中有一个进程存在则该进程组就存在这与其组长进程是否已经终止无关。2. 会话2.1 什么是会话刚刚我们谈到了进程组的概念那么会话又是什么呢会话其实和进程组息息相关会话可以看成是一个或多个进程组的集合一个会话可以包含多个进程组。每一个会话也有一个会话ID(SID)通常我们都是使用管道将几个进程编成一个进程组。如上图的进程组2和进程组3可能是由下列命令形成的[nodelocalhost code]$ proc2|proc3[nodelocalhost code]$ proc4|proc5|proc6# 表示将进程组放在后台执行我们举一个例子观察一下这个现象# 用管道和sleep组成一个进程组放在后台运行[nodelocalhost code]$sleep100|sleep200|sleep300# 查看ps命令打出来的列描述信息[nodelocalhost code]$psaxj|head-n1#过滤sleep相关的进程信息[nodelocalhost code]$psaxj|grepsleep|grep-vgrep# a 选项表示不仅列当前⽤户的进程也列出所有其他⽤户的进程# x 选项表示不仅列有控制终端的进程也列出所有⽆控制终端的进程# j 选项表示列出与作业控制相关的信息作业控制后续会讲# grep 的 -v 选项表示反向过滤即不过滤带有 grep 字段相关的进程# 结果如下PPIDPID PGID SIDTTY TPGIDSTATUIDTIME COMMAND2806422342232780pts/2 4229S10000:00sleep1002806422442232780pts/2 4229S10000:00sleep2002806422542232780pts/2 4229S10000:00sleep300从上述结果来看3个进程对应的PGID相同即属于同一个进程组。2.2 如何创建会话可以调用setseid函数来创建一个会话前提是调用进程不能是一个进程组的组长。#includeunistd.h/* *功能创建会话 *返回值创建成功返回SID,失败返回-1 */pid_tsetsid(void);该接口调用之后会发生调用进程会变成新会话的会话首进程。此时新会话中只有唯一的一个进程调用进程会变成进程组组长。新进程组ID就是当前调用进程ID该进程没有控制终端。如果在调用setsid之前该进程存在控制终端则调用之后会切断联系需要注意的是这个接口如果调用进程原来是进程组组长则会报错为了避免这种情况我们通常的使用方法是先调用fork创建子进程父进程终止子进程继续执行因为子进程会继承父进程的进程组ID,而进程ID则是新分配的就不会出现错误的情况。2.3 会话IDSID上边我们提到了会话ID那么会话ID是什么呢我们可以先说一下会话首进程会话首进程是具有唯一进程ID的单个进程那么我们可以将会话首进程的进程ID当做是会话ID。注意会话ID在有些地方也被称为会话首进程的进程组ID因为会话首进程总是一个进程组的组长进程所以两者是等价的。3. 控制终端先说一下什么是控制终端在UNIX系统中用户通过终端登录系统后得到一个Shell进程这个终端成为Shell进程的控制终端。控制终端是保存在PCB中的信息我们知道fork进程会复制PCB中的信息因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况下没有重定向每个进程的标准输入、标准输出和标准错误都指向控制终端进程从标准输入读也就是读用户的键盘输入进程往标准输出或标准错误输出写也就是输出到显示器上。另外会话、进程组以及控制终端还有一些其他的关系我们在下边详细介绍一下一个会话可以有一个控制终端通常会话首进程打开一个终端终端设备或伪终端设备后该终端就成为该会话的控制终端。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分成一个前台进程组以及一个或者多个后台进程组。如果一个会话有一个控制终端则它有一个前台进程组会话中的其他进程组则为后台进程组。无论何时进入终端的中断键ctrlc或退出键ctrl\就会将中断信号发送给前台进程组的所有进程。如果终端接口检测到调制解调器或网络已经断开则将挂断信号发送给控制进程会话首进程。这些特性的关系如下图所示4. 作业控制4.1 什么是作业job和作业控制Job Control作业是针对用户来讲用户完成某项任务而启动的进程一个作业既可以只包含一个进程也可以包含多个进程进程之间互相协作完成任务通常是一个进程管道。Shell 分前后台来控制的不是进程而是作业或者进程组。一个前台作业可以由多个进程组成一个后台作业也可以由多个进程组成Shell可以同时运⾏一个前台作业和任意多个后台作业这称为作业控制。例如下列命令就是一个作业它包括两个命令在执⾏时Shell将在前台启动由两个进程组成的作业[nodelocalhost code]$cat/etc/filesystems|head-n5运⾏结果如下所示xfs ext4 ext3 ext2 nodev proc4.2 作业号放在后台执⾏的程序或命令称为后台命令可以在命令的后面加上符号从而让Shell 识别这是一个后台命令后台命令不用等待该命令执⾏完成就可立即接收新的命令另外后台进程执行完后会返回一个作业号以及一个进程号PID。例如下面的命令在后台启动了一个作业该作业由两个进程组成两个进程都在后台运⾏:[nodelocalhost code]$cat/etc/filesystems|grepext执⾏结果如下[1]2202ext4 ext3 ext2# 按下回车[1] 完成cat/etc/filesystems|grep--colorauto ext第1⾏表示作业号和进程ID可以看到作业号是1进程ID是2202第2-4⾏表示该程序运⾏的结果过滤 /etc/filesystems 有关ext的内容第6行分别表示作业号、默认作业、作业状态以及所执⾏的命令关于默认作业对于一个用户来说只能有一个默认作业同时也只能有一个即将成为默认作业的作业-当默认作业退出后该作业会成为默认作业。表示该作业号是默认作业-表示该作业即将成为默认作业无符号表示其他作业4.3 作业状态常见的作业状态如下表所示4.4 作业的挂起与切回4.4.1 作业挂起我们在执⾏某个作业时可以通过CtrlZ键将该作业挂起然后Shell会显示相关的作业号、状态以及所执⾏的命令信息。例如我们运⾏一个死循环的程序通过CtrlZ将该作业挂起观察一下对应的作业状态#includestdio.hintmain(){while(1){printf(hello\n);}return0;}下面我运⾏这个程序通过CtrlZ将该作业挂起# 运行可执行程序[nodelocalhost code]$ ./test# 键入Ctrl Z观察现象运⾏结果如下# 结果依次对应作业号 默认作业 作业状态 运行程序信息[1] 已停止 ./test7可以发现通过 CtrlZ 将作业挂起该作业状态已经变为了停止状态4.4.1 作业切回如果想将挂起的作业切回可以通过fg命令fg后面可以跟作业号或作业的命令名称。如果参数缺省则会默认将作业号为1的作业切到前台来执⾏若当前系统只有一个作业在后台进⾏则可以直接使用fg命令不带参数直接切回。具体的参数参考如下例如我们把刚刚挂起来的./test作业切回到前台[nodelocalhost code]$fg%%运⾏结果为开始无限循环打印hello可以发现该作业已经切换到前台了。注意当通过fg命令切回作业时若没有指定作业参数此时会将默认作业切到前台执行即带有“”的作业号的作业4.5 查看后台执行或挂起的作业我们可以直接通过输入jobs命令查看本用户当前后台执⾏或挂起的作业参数 -l 则显示作业的详细信息参数 -p 则只显示作业的PID例如我们先在后台及前台运⾏两个作业并将前台作业挂起来用jobs命令查看作业相关的信息# 在后台运行一个作业sleep[nodelocalhost code]$sleep300# 运行刚才的死循环可执行程序[nodelocalhost code]$ ./test# 键入Ctrl Z 挂起作业# 使用jobs命令查看后台及挂起的作业[nodelocalhost code]$jobs-l运⾏结果如下所示# 结果依次对应作业号 默认作业 作业状态 运行程序信息[1]-2265运行中sleep300[2]2267停止 ./test74.6 作业控制相关的信号上面我们提到了键入 Ctrl Z 可以将前台作业挂起 实际上是将 STGTSTP 信号发送至前台进程组作业中的所有进程 后台进程组中的作业不受影响。 在 unix系统中 存在 3 个特殊字符可以使得终端驱动程序产生信号 并将信号发送至前台进程组作业 它们分别是Ctrl C 中断字符 会产生 SIGINT 信号Ctrl \ 退出字符 会产生 SIGQUIT 信号Ctrl Z 挂起字符 会产生 STGTSTP 信号终端的 I/O(即标准输入和标准输出)和终端产生的信号总是从前台进程组作业连接打破实际终端。 我们可以通过下图看到作业控制的功能5. 守护进程Daemon.hpp#pragmaonce#includeiostream#includecstdlib#includesignal.h#includeunistd.h#includefcntl.h#includesys/types.h#includesys/stat.hconstchar*root/;constchar*dev_null/dev/null;voidDaemon(boolischdir,boolisclose){// 1. 忽略可能引起程序异常退出的信号signal(SIGCHLD,SIG_IGN);signal(SIGPIPE,SIG_IGN);// 2. 让自己不要成为组长if(fork()0)exit(0);// 3. 设置让自己成为一个新的会话 后面的代码其实是子进程在走setsid();// 4. 每一个进程都有自己的 CWD 是否将当前进程的 CWD 更改成为 / 根目录if(ischdir)chdir(root);// 5. 已经变成守护进程啦 不需要和用户的输入输出 错误进行关联了if(isclose){close(0);close(1);close(2);}else{// 这里一般建议就用这种intfdopen(dev_null,O_RDWR);if(fd0){dup2(fd,0);dup2(fd,1);dup2(fd,2);close(fd);}}}6. 如何将服务守护进程化// ./server portintmain(intargc,char*argv[]){if(argc!2){std::coutUsage : argv[0] portstd::endl;return0;}uint16_tlocalportstd::stoi(argv[1]);Daemon(false,false);std::unique_ptrTcpServersvr(newTcpServer(localport,HandlerRequest));svr-Loop();return0;}