进程创建——fork()

1. 函数原型

pid_t fork(void);

pid_t实质是int类型,Linux内核2.4.0定义:

typedef int _kernel_pid_t;
typedef _kernel_t pid_t;

2. 函数功能
fork()函数会新生成一个进程,调用fork()函数的进程为父进程,新生成的进程为子进程。
fork()函数调用一次,返回两次,在父进程返回子进程的pid,在子进程返回0,失败则返回-1。

3. 函数用法

#include 		//头文件
int main()
{
	int p=fork();
	if(p==0)
	{
		func...;		//子进程
	}
	else
	{
		func...			//父进程
	}
	return 0;
}

4. 写时拷贝
fork()函数在生成子进程时用到了写时拷贝技术。其实现原理是,不执行一个父进程数据段、栈区、堆区的完全复制,这些区域将由父子进程共享,内核修改其访问权限为只读。如果父子进程中任何一个试图修改这些这些区域的数据,则内核只为被修改数据的相应内存制作一个副本,并且以虚拟存储系统中的“一页”为单位复制。

5. 内核实现原理
Linux通过clone()系统调用函数实现fork(),调用流程如下图:
进程创建——fork()_第1张图片

copy_process()函数的工作流程如下:

(1)调用dup_task_struct()为新进程创建一个内核栈、thread_info与task_struct结构,这些数据结构的值与当前进程的值相同,此时父子进程的描述符完全相同;

(2)检查创建新的子进程之后,当前用户所拥有的进程数目是否超出为其分配的资源限制;

(3)进程描述符内的成员都被清0或设为初始值,以保证将父子进程区分开来,但进程描述符中的大多数数据都是共享的;

(4)子进程的状态被设置为TASK_UNINTERRUPTIBLE,以保证其不会投入运行;

(5)copy_process()函数调用copy_flags()更新task_struct的flags成员,标识进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0,标识进程还未调用exec()函数的PF_FORKNOEXEC标志被重置;

(6)调用get_pid()为新进程获取一个有效的PID;

(7)根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命令空间等。一般这些资源被同一个进程中的所有线程共享,但是这些资源对每个进程都是相互独立的,所以需要拷贝;

(8)父子进程平分剩余的时间片;

(9)copy_process()做扫尾工作并返回一个指向子进程的指针。

Linux内核应该保证新创建的子进程先执行,一般子进程都会马上调用exec()函数,以此来避免写时拷贝的额外开销,如果父进程先执行,则有可能会向内存空间写入数据,从而发生写时拷贝,造成额外的资源开销。

6. vfork()函数
函数原型:

pid_t vfork(void)

特点:vfork()函数保证子进程先执行,只有当调用exec()或_exit()时父进程才会执行

7. 僵尸进程
僵尸进程也称为僵尸进程,是因为父进程未结束,子进程结束,并且父进程未获取到子进程的退出状态,这样的进程,进程主体空间已经释放,但进程控制块(PCB)还未释放。
处理办法:
(1)在父进程中调用wait()或waitpid()获取子进程的退出状态,这种方式可能会导致父进程在wait()或waitpid()阻塞运行,直到子进程退出;
(2)父进程调用signal(SIGCHLD,SIG_IGN)来忽略SIGCHLD信号,这样子进程结束后会由内核释放其PCB和其他资源;

8. 孤儿进程
孤儿进程是指父进程结束,子进程未结束,这样的进程被称为孤儿进程。孤儿进程会被系统进程init收养,并为他们完成状态收集工作

9. 守护进程
守护进程也称为精灵进程,常常在系统启动时自启,仅在系统关闭时才终止,生存周期较长,一般都是在后台运行。
ps -axj可以用来查看系统守护进程,其中最为常见的init进程,负责各运行层次间的系统命令

你可能感兴趣的:(Linux)