
下面说fork、vfork和clone三个函数。这三个函数分别调用了sys_fork、sys_vfork、sys_clone,最终都调用了do_fork函数,差别在于参数的传递和一些基本的准备工作不同。可见这三者最终达到的最本质的目的都是创建一个新的进程。在这里需要明确一下,Linux内核中没有独立的“线程”结构,Linux的线程就是轻量级进程,换言之基本控制结构和Linux的进程是一样的(都是通过struct task_struct管理)。
fork是最简单的调用,不需要任何参数,仅仅是在创建一个子进程并为其创建一个独立于父进程的空间。fork使用COW(写时拷贝)机制,并且COW了父进程的栈空间。
vfork是一个过时的应用,vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。vfork最初是因为fork没有实现COW机制,而很多情况下fork之后会紧接着exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了vfork。而现在fork使用了COW机制,唯一的代价仅仅是复制父进程页表的代价,所以vfork不应该出现在新的代码之中。在Linux的manpage中队vfork有这样一段话:It is rather unfortunate that Linux revived this specter from the past. The BSD man page states: "This system call will be eliminated when proper system sharing mechanisms are implemented. Users should not depend on the memory sharing semantics of vfork() as it will, in that case, be made synonymous to fork(2)."
clone是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。clone和fork的调用方式也很不相同,clone调用需要传入一个函数,该函数在子进程中执行。此外,clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。
关于Linux命令的介绍,看看《linux就该这么学》,具体关于这一章地址3w(dot)linuxprobe/chapter-02(dot)html
1、守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程。2、创建守护进程步骤
1)创建子进程,父进程退出
之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。
在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程。
2)在子进程中创建新会话
进程组:是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因组长进程的退出而受到影响。
会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。
(1)pid_t setsid(void)
setsid() creates a new session if the calling process is not a process group leader. The calling process will be the only process in this new process group and in this new session.
setsid函数用于创建一个新的会话,并担任该会话组的组长。调用setsid有下面的3个作用:
① 让进程摆脱原会话的控制
② 让进程摆脱原进程组的控制
③ 让进程摆脱原控制终端的控制
有以下三个结果:
(a)成为新会话的首进程
(b)成为一个新进程组的组长进程
(c)没有控制终端。
有些人建议在此时再次调用fork,并使父进程终止。第二个子进程作为守护进程继续运行。这样就保证了该守护进程不是会话首进程。
setsid函数能够使进程完全独立出来,从而摆脱其他进程的控制。
setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。 子进程可以自己组成一个新的进程组,即调用setpgrp()与原进程组脱离关系,产生一个新的进程组,进程组号与它的进程号相同.这样,父进程退出运行后就不会影响子进程的当前运行.
3)改变当前目录为根目录
使用fork创建的子进程继承了父进程的当前工作目录;进程活动时,其工作目录所在的文件系统不能卸下。通常的做法是让"/"作为守护进程的当前工作目录,也可以是其他目录,如/tmp,使用chdir。
4)重设文件权限掩码
文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。mask = mask &~050
通常,把文件权限掩码设置为0,umask(0)。
5)关闭文件描述符
用fork函数新建的子进程会从父进程那里继承已经打开了的文件描述符。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。
在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭。
for(i=0i<MAXFILEi++)
close(i)
6)守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。
signal(SIGTERM, sigterm_handler)
void sigterm_handler(int arg)
{
_running = 0
}
7)处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的 *** 作设为SIG_IGN。
signal(SIGCHLD,SIG_IGN)
这样,内核在子进程结束时不会产生僵尸进程。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)