基本概念
linux
1、进程shell
进程与程序的区别:程序是指的存储在存储设备上(如磁盘)包含了可执行机器指 令(二进制代码)和数据的静态实体;而进程能够认为是已经被OS从磁盘加载到内存上的、动态的、可运行的指令与数据的集合,是在运行的动态实体。
bash
2、进程组服务器
每一个进程除了有一个进程ID以外,还属于一个进程组。进程组是一个或多个进程的集合。一般,它们与同一做业相关联,能够接收来自同一终端的各类信号。每一个进程组有一个惟一的进程组ID。每一个进程组均可以有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID。网络
组长进程能够建立一个进程组,建立该组中的进程,而后终止。只要在某个进程组中一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。ide
3、做业函数
Shell分先后台来控制的不是进程而是做业(Job)或者进程(Process Group)。一个前台做业能够由多个进程组成,一个后台也能够由多个进程组成,Shell能够运行一个前台做业和任意多个后台做业,这称为做业控制。spa
做业与进程组的区别:若是做业中的某个进程又建立了子进程,则子进程不属于做业。一旦做业运行结束,Shell就把本身提到前台,若是原来的前台进程还存在(若是这个子进程还没终止),它自动变为后台进程组。3d
示例:orm
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { int id = fork(); if(id < 0) { perror("fork"); return -1; } else if(id == 0) //child { while(1) { printf("child\n"); sleep(1); } } else //father { int count = 0; while(count < 5) { printf("father\n"); ++count; sleep(1); } exit(0); } return 0; }
父子进程同时向终端输出信息,5秒以后父进程退出,但子进程仍在继续。(只能经过kill命令杀死该进程)
4、会话
会话(Session)是一个或多个进程组的集合。
一个会话能够有一个控制终端。这一般是登录到其上的终端设备(在终端登录状况下)或伪终端设备(在网络登录状况下)。创建与控制终端链接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。因此一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。
示例:
其中proc1与proc2属于同一个后台进程组,proc3,proc4和proc5属于同一个前台进程组,Shell自己属于一个单独的进程组。这些进程组的控制终端相同,它们同属于一个会话,当用户在控制终端输入特殊的控制键(如Ctrl+C,产生SIGINT,Ctrk+\,产生SIGQUIT,Ctrl+Z,产生SIGTSTP),内核发送相应的信号给前台进程组中的全部进程。
5、终端
在UNIX系统中,用户经过终端登陆系统后获得一个Shell进程,这个终端成为Shell进程的控制终端 (Controlling Terminal),控制终端是保存在PCB中的信息,而咱们知道fork会复制PCB中的信息,所以由Shell进程启动的其它进程的控制终端也是这个终端。默认状况下(没有重定向),每一个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。此外在控制终端输入一些特殊的控制键能够给前台进程发信号,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。
每一个进程均可以经过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每一个终端设备都对应一个不一样的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既能够经过/dev/tty也能够经过该终端设备所对应的设备文件来访问。ttyname函数能够由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不能是任意文件。
示例:
#include <stdio.h> int main() { printf("fd: %d -> %s\n",0, ttyname(0)); printf("fd: %d -> %s\n",1, ttyname(1)); printf("fd: %d -> %s\n",2, ttyname(2)); return 0; }
再打开一个终端运行该程序:
再打开一个终端运行该程序:
将终端0的运行结果重定向到终端1
咱们发现,终端1的标准输出变成了终端1的。而标准输入和标准错误没有变化,仍是终端0
做业控制
会话(Session)与进程组
事实上,Shell分先后台来控制的不是进程而是做业 (Job)或者进程组(ProcessGroup)。一个前台做业能够由多个进程组成,一个后台做业也能够由多个进程组成,Shell能够同时运行一个前台做业和任意多个后台做业,这称为做业控制(Job Control)。
其中proc1和proc2属于同一个后台进程组,proc三、proc四、proc5属于同一个前台进程组,Shell进程自己属于一个单独的进程组。这些进程组的控制终端相同,它们属于同一个Session。当用户在控制终端输入特殊的控制键(例如Ctrl-C)时,内核会发送相应的信号(例如SIGINT)给前台进程组的全部进程。
shell执行命令的过程:
由Shell进程fork出的子进程原本具备和Shell相同的Session、进程组和控制终端,可是Shell调用setpgid函数将做业中的某个子进程指定为一个新进程组的Leader,而后调用setpgid将该做业中的其它子进程也转移到这个进程组中。若是这个进程组须要在前台运行,就调用tcsetpgrp函数将它设置为前台进程组,因为一个Session只能有一个前台进程组,因此Shell所在的进程组就自动变成后台进程组。在上面的例子中,proc三、proc四、proc5被Shell放到同一个前台进程组,其中有一个进程是该进程组的Leader,Shell调用wait等待它们运行结束。一旦它们所有运行结束,Shell就调用tcsetpgrp函数将本身提到前台继续接受命令。可是注意,若是proc三、proc四、proc5中的 某个进程又fork出子进程,子进程也属于同一进程组,可是Shell并不知道子进程的存在,也不会调用wait等待它结束。换句话说,proc3 | proc4 | proc5是Shell的做业,而这个子进程不是,这是做业和进程组在概念上的区别。一旦做业运行结束,Shell就把本身提到前台,若是原来的前台进程组还存在(若是这个子进程还没终止),则它自动变成后台进程组。
示例:
这个做业由ps和cat两个进程组成,在前台运行。从PPID列能够看出这两个进程的父进程是bash。从PGRP列能够看出,bash在id为5122的进程组中,这个id等于bash的进程id,因此它是进程组的Leader,而两个子进程在id为6576的进程组中,ps是这个进程组的Leader。从SESS能够看出三个进程都在同一Session中,bash是Session Leader。从TPGID能够看出,前台进程组的id是6576,也就是两个子进程所在的进程组。
这个做业由ps和cat两个进程组成,在后台运行,bash不等做业结束就打印提示信息[1] 7141而后给出提示符接受新的命令,[1]是做业的编号,若是同时运行多个做业能够用这个编号区分,7141是该做业中某个进程的id。
与做业控制有关的信号
将cat放到后台运行,因为cat须要读标准输入(也就是终端输入),然后台进程是不能读终端输入的,所以内核发SIGTTIN信号给进程,该信号的默认处理动做是使进程中止。
jobs命令能够查看当前有哪些做业。fg命令能够将某个做业提至前台运行,若是该做业的进程组正在后台运行则提至前台运行,若是该做业处于中止状态,则给进程组的每一个进程发SIGCONT信号使它继续运行。参数%1表示将第1个做业提至前台运行。cat提到前台运行后,挂起等待终端输入,当输入hello并回车后,cat打印出一样的一行,而后继续挂起等待输入。若是输入Ctrl-Z则向全部前台进程发SIGTSTP信号,该信号的默认动做是使进程中止,cat继续之后台做业的形式存在。
bg命令可让某个中止的做业在后台继续运行,也须要给该做业的进程组的每一个进程发SIGCONT信号。cat进程继续运行,又要读终端输入,然而它在后台不能读终端输入,因此又收到SIGTTIN信号而中止。
用kill命令给一个中止的进程发SIGTERM信号,这个信号并不会马上处理,而要等进程准备继续运行以前处理,默认动做是终止进程。但若是给一个中止的进程发SIGKILL信号就不一样了。以下:
SIGKILL信号既不能被阻塞也不能被忽略,也不能用自定义函数捕捉,只能按系统的默认动做马上处理。与此相似的还有SIGSTOP信号,给一个进程发SIGSTOP信号会使进程中止,这个默认的处理动做不能改变。这样保证了无论什么样的进程都能用SIGKILL终止或者用SIGSTOP中止,当系统出现异常时管理员老是有办法杀掉有问题的进程或者暂时停掉怀疑有问题的进程。
守护进程
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种颇有用的进程。Linux的大多数服务器就是用守护进程实现的。好比,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。好比,做业规划进程crond,打印进程lpd等。(这里的结尾字母d就是Daemon的意思)
linux建立守护进程的步骤以下:
建立守护进程最关键的一步是调用setsid函数建立一个新的Session,并成为Session Leader。该函数调用成功时返回新建立的Session的id(其实也就是当前进程的id),出错返回-1。注意,调用这个函数以前,当前进程不容许是进程组的Leader,不然该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就好了。fork建立的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,因此子进程不多是该组的第一个进程,在子进程中调用setsid就不会有问题了。
成功调用该函数的结果是:
1. 建立一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
2. 建立一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
3. 若是当前进程本来有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然能够读写,但只是一个普通的打开文件而不是控制终端了。
建立守护进程
1. 调用umask将文件模式建立屏蔽字设置为0.
2. 调用fork,父进程退出(exit)。
缘由:
1)若是该守护进程是做为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。
2)保证子进程不是一个进程组的组长进程。
3. 调用setsid建立一个新会话。setsid会致使:
1)调用进程成为新会话的首进程。
2)调用进程成为一个进程组的组长进程 。
3)调用进程没有控制终端。(再次fork一次,保证daemon进程,以后不会打开tty设备)
4. 将当前工做目录更改成根目录。
5. 关闭不在须要的文件描述符。
6. 其余:忽略SIGCHLD信号。