【进程组】 进程组是一个或多个进程的集合。每个进程除了有一个进程ID之外,还属于一个进程组。 每个进程组有一个唯一的进程组ID。每个进程组都可以有一个组长进程。 组长进程的进程组ID等于其进程ID。进程组是否存在和有无组长无关。 每个进程都是属于进程组的,没有独立的进程,除非该进程组中只有一个进程,则可以说这个进程是独立的。 组长进程可以创建一个进程组,创建该组中的进程,然后终止。 但是只要有一个进程存在,则这个进程组就存在,这与其组长进程是否终止无关。 通常,它们与同一作业相关联,可以接收来自同一终端的各种信号。 【作业】 Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。 (1)作业控制: 一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成, Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。 后台程序将不会被ctrl+c杀死 当我们再一个终端开了一个后台程序,关闭终端后,后台程序也就停了。 (2)作业与进程组的区别: 如果作业中的某个进程又创建了子进程,则子进程不属于作业,但是该子进程属于其进程组。 一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程组。 【会话】 会话(Session)是由多个作业或者进程组构成,是一个或多个进程组的集合。 一个会话可以有一个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或伪终端设备(在网络登陆情况下)。 建立与控制终端连接的会话首进程被称为控制进程。 一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。 所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。 打开一个终端,就是开始一个会话,再一个会话里运行一个进程,可以在别的终端查看其状态,当kill命令下达后,系统将其kill 查看终端设备 每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端,既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问。ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不 能是任意文件。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() { printf("%d ----> %d \n",0,ttyname(0)); printf("%d ----> %d \n",1,ttyname(1)); printf("%d ----> %d \n",2,ttyname(2)); return 0; } 我的结果不是目录是一个地址: 0 ----> 140038152 1 ----> 140038152 2 ----> 140038152 再开一个运行如下: 0 ----> 13456××× 1 ----> 13456××× 2 ----> 13456××× 【守护进程】 守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。 守护进程是生存期长的一种进程。它们常常在系统引导装入时启动,仅在系统关闭时才终止。因为它们没有控制终端,所以说它们是在后台运行的。 守护进程的特点: (1)Linux系统启动时会启动很多系统服务进程,守护进程没有控制终端,不能直接和用户交互 (2)其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但守护进程不受用户登录注销的影响,只受开机或者关机的影响。 在系统查看中ps ajx |grep -ER 'd]$' [sts@localhost 20160729]$ ps ajx |grep -ER 'd]$' 0 2 0 0 ? -1 S 0 0:00 [kthreadd] 2 17 0 0 ? -1 S 0 0:00 [kacpid] 2 22 0 0 ? -1 S 0 0:00 [ksuspend_usbd] 2 23 0 0 ? -1 S 0 0:00 [khubd] 2 24 0 0 ? -1 S 0 0:00 [kseriod] 2 28 0 0 ? -1 S 0 0:00 [khungtaskd] 2 30 0 0 ? -1 SN 0 0:00 [ksmd] 2 38 0 0 ? -1 S 0 0:00 [pciehpd] 2 40 0 0 ? -1 S 0 0:00 [kpsmoused] 2 71 0 0 ? -1 S 0 0:00 [kstriped] 2 954 0 0 ? -1 S 0 0:00 [kauditd] [统一 -1的tpgid] 与终端无关 自成用户组,自成会话,可以创建子线程 守护进程存在的原因: daemon函数存在的原因是因为控制终端由于某些原因(如断开终端链接)会发送一些信号的原因。 而接收处理这些信号的缺省动作会让进程退出。这些信号会由于终端上敲一些特殊按键而产生。 守护进程和后台进程的区别: (1)守护进程是后台进程,后台进程不一定是守护进程 (2)守护进程运行是与终端无关的,是不能往终端上打消息的 (3)守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行 创建守护进程: 创建守护进程最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。 #include<unistd.h> pid_t setsid(void); 该函数调用成功时返回新创建的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出子进程,然后子进程调用setsid,父进程直接退出(保证了子进程不是一个进程组的组长), (3)调用setsid创建一个新的会话(调用成功会使调用进程成为新会话的首进程, 并且成为一个进程组的组长进程,调用进程没有控制终端) (4)将当前工作目录更改为根目录 (5)关闭不在需要的文件描述符 (6)忽略SIGCHLD信号 #include<stdio.h> #include<stdlib.h> #include<signal.h> #include<unistd.h> #include<fcntl.h> #include<sys/stat.h> void create_daemon() { int i,f; pid_t pid; struct sigaction sa; umask(0); if(pid=fork()<0); if(pid!=0) exit(0); setsid(); sa.sa_handler=SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags=0; if(sigaction(SIGCHLD,&sa,NULL)<0) return ; if(pid=fork()<0) return; if(pid!=0) exit(0); if(chdir("/")<0) return; close(0); f=open("/dev/null",O_RDWR); dup2(f,1); dup2(f,2); } int main() { create_daemon(); while(1) { sleep(1); } getchar(); } 编译运行后,ps ajx查看 PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 1 14550 14550 14550 ? -1 Ss 500 0:00 ./a.out 14550 14552 14550 14550 ? -1 S 500 0:00 ./a.out (1)调用一次fork的作用: 第一次fork的作用是让shell认为这条命令已经终止,不用挂在终端输入上,还有就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork出子进程,则此时的父进程是进程组组长,就无法调用setsid。当子进程调用完setsid函数之后,子进程是会话组长也是进程组组长,并且脱离了控制终端,此时,不管控制终端如何操作,新的进程都不会收到一些信号使得进程退出。 (2)第二次fork的作用: 虽然当前关闭了和终端的联系,但是后期可能会误操作打开了终端。 只有会话首进程能打开终端设备,也就是再fork一次,再把父进程退出,再次fork的子进程作为守护进程继续运行,保证了该精灵进程不是对话期的首进程, 第二次不是必须的,是可选的,市面上有些开源项目也是fork一次 进程 前后台切换的方式 程序 int main() { while(1) { printf("wzzx"); } getchar(); } 运行 [sts@localhost 20160729]$ ./a.out zxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzx^Z [1]+ Stopped ./a.out [sts@localhost 20160729]$ jobs 查看 [1]+ Stopped ./a.out [sts@localhost 20160729]$ fg 1 前台运行 (死循环打印中 能被ctrl+z 或ctrl+c 处理掉) zxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzxwzzx^Z [sts@localhost 20160729]$ bg 1 后台运行 (死循环打印中 不能被ctrl+z 或ctrl+c 处理掉 仅仅关闭终端会话)
/*
http://blog.csdn.net/asd7486/article/details/51956929
http://blog.csdn.net/yh1548503342/article/details/41891047
http://blog.sina.com.cn/s/blog_6642cd020101g3tl.html
*/