进程的创建:
Linux通过两个步骤创建新的进程:fork()和exec().其中fork可以创建当前进程(父进程)的一个副本,即子进程。父进程和子进程只有PID不同。在这之后,系统中有两个进程,执行同样的操作。父进程的内容将被复制,但是在Linux中运用了一种写时复写(copy on write)技术,使进程的创建更为高效。接下来exec将读取可执行文件载入地址空间中运行。这样一个进程就创建好啦!
其中fork系统调用的入口点为sys_fork,该函数和体系结构有关,任务是从处理器的寄存器中提取由用户空间提供的信息,并且调用与体系结构无关的do_fork()函数,进行进程复制。然后do_fork主要进行以下几项工作,进行进程生成的实际工作:
1.调用copy_process复制进程。
2.获取新进程的PID
3.用wake_up_new_taskhuanxing唤醒子进程,即将它的task_struct添加到调度器队列中,但这并不意味着它可以立即执行,而是调度器此时可以选择它执行。
其中进程的复制又包括以下几个步骤:
1.检查各种标识,类似于CLONE_FS(与父进程共享文件系统)等待,有些标识的组合是没有意义的,所以需要检查
2.调用dup_task_struct创建父进程task_struct的副本。父子进程的task_struct进程只有核心栈态这个成员不同
3.检查资源限制,因为每个用户能拥有的进程数是有限的,超过则要放弃创建进程
4.调用接口函数sched_fork,以便使调度器对新进程进行设置。防止内核的其他任何部分在进程设置完成前调度进程
5.调用许多类似于copy_xyz的例程,复制和共享内核子系统的各种资源
6.设置各个ID和进程关系
这样整个进程就复制结束啦!
exec()的实现,即用新代码代替旧代码,可启动新程序。
该系统调用的入口点是和体系结构相关的sys_execve函数,但是该函数很快将工作交给与系统结构无关的do_execve例程,该例程主要进行以下几个工作:
1.打开可执行文件
2.调用bprm_init来处理若干管理性事务:生成mm_alloc管理进程地址空间,init_new_context初始化该实例,__bprm_mm_init建立初始的栈
3.调用prepare_binprm提供父进程的一些相关值
4.复制环境和参数数组内容
5.调用search_binary_handler,在do_execve结束时查找一种适当的二进制格式,用于所要执行的文件。而二进制格式处理程序用于将新程序的数据加载到旧的地址空间中
这样,一个新的程序就开始执行啦!
进程的终结:
进程要终结的时候会调用exit()函数,不过和前面一样,最终都还是通过调用do_exit()这个函数来结束进程的。简单的说,就是将各个引用计数器减一,若有计数器归0,则释放相应的内存。然后调用exit_notify()告知父进程为子进程找养父,并把进程状态设置为EXIT_ZOMBIE.最后调用schedule()切换到其他进程。自此该进程不会被再次调用。此时该进程占用的内存仅为内存栈,thread_info和task_struct。当父进程检测到僵尸进程提供的信息之后,将进程所持有的剩余内存释放。至此进程终结。
Linux------进程的创建和终结,布布扣,bubuko.com