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() 函数:

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 
  4 int main(){
  5     pid_t fpid;
  6     int count= 0;
  7     fpid = fork();              // fpid 为fork()的返回值
  8     if(fpid < 0){               // 当fork()的返回值为负值时,表明调用 fork() 出错
  9         printf("error in fork!");
 10     }
 11     else if(fpid  == 0){        // fork() 返回值为0,表明该进程是子进程
 12         printf("this is a child process, the process id is %d\n",getpid());
 13         count++;
 14     }
 15     else{                       // fork() 返回值大于0,表明该进程是父进程,这时返回值其实是子进程的PID
 16         printf("this is a father process, the process id is %d\n",getpid());
 17         count++;
 18     }
 19     printf("计数 %d 次\n",count);
 20     return 0;                                                                          
 21 }

出口结果:

图片 1

  可以见到,调用 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

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 #include <errno.h>
  4 #include <string.h>
  5 
  6 int main(){
  7     int childpid;
  8     pid_t fpid;
  9     fpid = fork();
 10     if(fpid == 0){                          // 子进程
 11         char *execv_str[] = {"ps","aux",NULL};      // 指令:ps aux 查看系统中所有进程 
 12         if( execv("/usr/bin/ps",execv_str) < 0 ){
 13             perror("error on exec\n");
 14             exit(0);
 15         }
 16     }
 17     else{
 18         wait(&childpid);
 19         printf("execv done\n");
 20     }
 21 }

 

   在那些顺序中,使用 fork() 创制了一个子经过,随后立时调用 exec()
函数簇中的 execv() 函数,execv()
函数执行了一条指令,突显当前系统中兼有的长河,结果如下(进度有众多,那里只截了一有些):

图片 2

图片 3

  注意看最后多少个进程,分别是父进度和调用 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() 来刑满释放进程描述符。

 

相关文章