Linux内核学习笔记(3)-- 进程的创建和终结
一、 进程创建:
Unix 下的进程创建很特别,与许多其他操作系统不同,它分两步操作来创建和执行进程: fork() 和 exec() 。首先,fork() 通过拷贝当前进程创建一个子进程;然后,exec() 函数负责读取可执行文件并将其载入地址空间开始运行。
1、fork() :kernel/fork.c
在Linux系统中,通过调用fork()来创建一个进程。调用 fork() 的进程称为父进程,新产生的进程称为子进程。在该调用结束时,在返回点这个相同的位子上,父进程恢复执行,子进程开始执行。fork()系统调用从内核返回两次:一次返回到父进程,另一次返回到新产生的子进程。使用fork()创建新进程的流程如下:
1)fork() 调用clone;
2)clone() 调用 do_fork();
3)do_fork() 调用 copy_process() 函数,copy_process() 函数将完成第 4-11 步;
4)调用 dup_task_struct() 为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同;
5)检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制;
6)清理子进程进程描述符中的一些成员(清零或初始化,如PID),以使得子进程与父进程区别开来;
7)将子进程的状态设置为 TASK_UNINTERRUPTIBLE,保证它不会投入运行;
8)调用 copy_flags() 以更新 task_struct 的 flags 成员;
9)调用 alloc_pid() 为新进程分配一个有效的 PID;
10)根据传递给clone() 的参数标志,copy_process() 拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等;
11)做一些扫尾工作并返回一个指向子进程的指针。
12)回到 do_fork() 函数,如果 copy_process() 函数成功返回,新创建的子进程将被唤醒并让其投入运行。
下面用一段简单的代码演示一下 fork() 函数:
#include <unistd.h>
#include <stdio.h> int main(){
pid_t fpid;
int count= ;
fpid = fork(); // fpid 为fork()的返回值
if(fpid < ){ // 当fork()的返回值为负值时,表明调用 fork() 出错
printf("error in fork!");
}
else if(fpid == ){ // fork() 返回值为0,表明该进程是子进程
printf("this is a child process, the process id is %d\n",getpid());
count++;
}
else{ // fork() 返回值大于0,表明该进程是父进程,这时返回值其实是子进程的PID
printf("this is a father process, the process id is %d\n",getpid());
count++;
}
printf("计数 %d 次\n",count);
return ;
}
输出结果:
可以看到,调用 fork() 函数后,原本只有一个进程,变成了两个进程。这两个进程除了 fpid 的值不同外几乎完全相同,它们都继续执行接下来的程序。由于 fpid 的值不同,因此会进入不同的判断语句,这也是为什么两个结果有不同之处的原因。另外,可以看到,父进程的 PID 刚好比子进程的 PID 小1。 fork() 的返回值有以下三种:
a)在父进程中,fork() 返回新创建子进程的 PID;
b)在子进程中,fork() 返回0;
c)如果 fork() 调用出错,则返回负值
2、exec() :fs/exec.c (源程序 exec.c 实现对二进制可执行文件和 shell 脚本文件的加载与执行)
通常,创建新的进程都是为了立即执行新的、不同的程序,而接着调用 exec() 这组函数就可以创建新的地址空间,并把新的程序载入其中。
exec() 并不是一个函数,而是一个函数簇,一共包含六个函数,分别为: execl、execlp、execle、execv、execvp、execve,定义如下:
#include <unistd.h> int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
这六个函数的功能其实差不多,只是接受的参数不同。exec() 函数的参数主要有3个部分:执行文件部分、命令参数部分和环境变量部分:
1)执行文件部分:也就是函数中的 path 部分,该部分指出了可执行文件的查找方式。其中 execl、execle、execv、execve的查找方式都是使用的绝对路径,而 execlp和execvp则可以只给出文件名进行查找,系统会从环境变量 "$PATH"中查找相应的路径;
2)命令参数部分:也就是函数中的 file 部分,该部分指出了参数的传递方式以及要传递哪些参数。这里,"l"结尾的函数表示使用逐个列举的方式传递参数;"v"结尾的表示将所有参数整体构造成一个指针数组进行传递,然后将该数组的首地址当做参数传递给它,数组中的最后一个指针要求为 NULL;
3)环境变量部分:exec() 函数簇使用了系统默认的环境变量,也可以传入指定的环境变量。其中 execle 和execve 这两个函数就可以在 envp[] 中指定当前进程所使用的环境变量。
· 当 exec() 执行成功时,exec() 函数会取代执行它的进程,此时,exec() 函数没有返回值,进程结束。当 exec() 函数执行失败时,将返回失败信息(返回 -1),进程继续执行后面的代码。
通常,exec() 会放在 fork() 函数的子进程部分,来替代子进程继续执行,exec() 执行成功后子进程就会消失,但是执行失败的话,就必须要使用 exit() 函数来让子进程退出。下面用一段简单的代码来演示一下 exec() 函数簇中的一个函数的用法,其余的参考:https://www.cnblogs.com/dongguolei/p/8098181.html
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> int main(){
int childpid;
pid_t fpid;
fpid = fork();
if(fpid == ){ // 子进程
char *execv_str[] = {"ps","aux",NULL}; // 指令:ps aux 查看系统中所有进程
if( execv("/usr/bin/ps",execv_str) < ){
perror("error on exec\n");
exit();
}
}
else{
wait(&childpid);
printf("execv done\n");
}
}
在这个程序中,使用 fork() 创建了一个子进程,随后立即调用 exec() 函数簇中的 execv() 函数,execv() 函数执行了一条指令,显示当前系统中所有的进程,结果如下(进程有很多,这里只截了一部分):
注意看最后两个进程,分别是父进程和调用 fork() 后创建的子进程。
二、进程终结
进程被创建后,最终要终结。当一个进程终结时,内核必须释放它所占有的资源,并把这一消息告诉其父进程。系统通过 exit() 系统调用来处理终止和退出进程的相关工作,而大部分工作则由 do_exit() 来完成 (kernel/exit.c):
1)将task_struct 中的标志成员设置为 PF_EXITING;
2)调用 del_timer_sync() 删除任一内核定时器,以确保没有定时器在排队,也没有定时器处理程序在运行;
3)调用 exit_mm() 函数释放进程占用的 mm_struct,如果没有别的进程使用它们(地址空间被共享),就彻底释放它们;
4)调用 sem__exit() 函数,如果进程排队等候 IPC 信号,它则离开队列;
5)调用 exit_files() 和 exit_fs(),以分别递减文件描述符、文件系统数据的引用计数,若其中某个引用计数的值降至零,则表示没有进程使用相应的资源,可以释放掉进程占用的文件描述符、文件系统资源;
6)把 task_struct 的 exit_code 成员设置为进程的返回值;
7)调用 exit_notify() 向父进程发送信号,并把进程状态设置为 EXIT_ZOMBIE;
8)调用 schedule() 切换到新的进程,继续执行。由于 EXIT_ZOMBIE 状态的进程不会被再调度,所以这是进程所执行的最后一段代码, do_exit() 没有返回值。
至此,与进程相关联的所有资源都被释放掉了,进程不可运行并处于 EXIT_ZOMBIE 退出状态。此时,进程本身所占用的内存还没有释放,如内核栈、thread_info 结构和 task_struct 结构等,它存在的意义是向父进程提供信息,当父进程收到信息后,或者通知内核那是无关的信息后,进程所持有的剩余的内存将被释放。父进程可以通过 wait4() 系统调用查询子进程是否终结,这其实使得进程拥有了等待特定进程执行完毕的能力。
系统通过调用 release_task() 来释放进程描述符。
Linux内核学习笔记(3)-- 进程的创建和终结的更多相关文章
- Linux内核学习笔记-2.进程管理
原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- Linux内核学习笔记二——进程
Linux内核学习笔记二——进程 一 进程与线程 进程就是处于执行期的程序,包含了独立地址空间,多个执行线程等资源. 线程是进程中活动的对象,每个线程都拥有独立的程序计数器.进程栈和一组进程寄存器 ...
- Linux内核学习笔记之seq_file接口创建可读写proc文件
转自:http://blog.csdn.net/mumufan05/article/details/45803219 学习笔记与个人理解,如有错误,欢迎指正. 温馨提示:建议跟着注释中的编号顺序阅读代 ...
- Linux内核学习笔记-1.简介和入门
原创文章,转载请注明:Linux内核学习笔记-1.简介和入门 By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- 20135316王剑桥Linux内核学习笔记
王剑桥Linux内核学习笔记 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 计算机是如何工作的 个人理 ...
- Linux内核分析 笔记六 进程的描述和进程的创建 ——by王玥
一.知识点总结 (一)进程的描述 1.操作系统内核里有三大功能: 进程管理 内存管理 文件系统 2.进程描述符:task_struct 2.进程描述符——struct task_struct 1. p ...
- (笔记)Linux内核学习(二)之进程
一 进程与线程 进程就是处于执行期的程序,包含了独立地址空间,多个执行线程等资源. 线程是进程中活动的对象,每个线程都拥有独立的程序计数器.进程栈和一组进程寄存器. 内核调度的对象是线程而不是进程.对 ...
- Linux内核学习笔记(1)-- 进程管理概述
一.进程与线程 进程是处于执行期的程序,但是并不仅仅局限于一段可执行程序代码.通常,进程还要包含其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个 ...
- linux内核学习之四:进程切换简述
在讲述专业知识前,先讲讲我学习linux内核使用的入门书籍:<深入理解linux内核>第三版(英文原版叫<Understanding the Linux Kernel>),不过 ...
随机推荐
- java中NULL与" "的区别
null是没有地址""是有地址但是里面的内容是空的 null和""的区别 问题一: null和""的区别 String s=null; st ...
- CC2540 OSAL 学习其中原理,以及 给任务 添加 一个事件(定时发送串口消息)
参考学习大神博客: http://blog.csdn.net/feilusia/article/details/51083953 : http://blog.csdn.net/xiaoleiacmer ...
- ubuntu下USB口插入USB转TTL查看串口号
首先先要获取权限 sudo su 然后 cd /devls ls可以列出所有的串口号(确保此时USB转TTL已经插在电脑上了) 然后拔掉USB转TTL 在ls一下列出所有的串口设备 对比可以发现,插上 ...
- Oracle11gR2(ASM,UDEV)的RAC搭建安装
基本信息: 1) 安装包: 操作系统:rhel-server-6.7-x86_64-dvd.iso rac安装包: Oracle11gR2:linux.x64_11gR2_database_1of2. ...
- OC中的内省(Introspection)方法
我们在写OC代码的时候经常用到:isKindOfClass: 一类的方法,但是对于它并没有一个了解,这里也是从网上搜索了一些内容,简单介绍并记录一下.这类方法就是属于OC的特性之一:内省. 内省(In ...
- 【js】Redux基本原理和使用
Redux不是说任何的应用都要用到它,如果遇到了react解决不了得问题,可以考虑使用它. 例如: 用户的使用方式复杂不同身份的用户有不同的使用方式(比如普通用户和管理员)多个用户之间可以协作与服务器 ...
- shell习题第3题:统计内存大小
[题目要求] 写一个脚本计算一下linux系统所有进程占用内存的大小的和 [核心要点] ps命令用法 for循环 加法运算 [脚本] #!/bin/bash for n in `ps aux | gr ...
- 每天一个Linux命令之mkdir
Linux mkdir命令 mkdir [-p] filename 用于创建一个空目录 如果该目录下有相同名称的目录那么会报错 apple@apple-Pro ~/Documents/java_d ...
- css3动画性能优化--针对移动端卡顿问题
一.使用css,jquery,canvas制作动画 1.Canvas 优点:性能好,强大,支持多数浏览器(除了IE6.IE7.IE8),画出来的图形可以直接保存为 .png 或者 .jpg的图形: 缺 ...
- (数据科学学习手札55)利用ggthemr来美化ggplot2图像
一.简介 R中的ggplot2是一个非常强大灵活的数据可视化包,熟悉其绘图规则后便可以自由地生成各种可视化图像,但其默认的色彩和样式在很多时候难免有些过于朴素,本文将要介绍的ggthemr包专门针对原 ...