Linux进程的创建过程
进程
进程的产生极大地提高的cpu的利用率,进程是cpu的执行单元。cpu可以通过进程切换提高使用率。那么Linux的进程是如何创建的呢?
这里一副图,我们先看个大概:
1、首先,我们得知道,我们的进程准备要干什么,这就需要写程序。如图右上角的.h .c文件,可以通过gcc编译成.o(可重定位目标文件,它是ELF(Relocatable File)文件的一种格式),里面包含了.text:编译好的二进制可执行代码,.data:已经初始化好的全局变量等,具体如下:
.text:放编译好的二进制可执行代码
.data:已经初始化好的全局变量
.rodata:只读数据,例如字符串常量、const 的变量
.bss:未初始化全局变量,运行时会置 0
.symtab:符号表,记录的则是函数和变量
.strtab:字符串表、字符串常量和变量名
这与我们java代码编译是同理的。
2、编译好文件之后,因为每个文件是松散的,互相都不知道各自有哪些可以调用的函数,属于两耳不闻窗外事的情况,无法协作完成一项任务。此时就需要进行链接。链接可分为静态链接和动态链接。
动态链接会创建一个动态链接库.so,它可以使得多个进程共享这些方法,类似于全局变量,静态链接只能被一个进程使用,类似于局部变量。
链接的过程在JVM加载class文件时也是同理的
3、链接完之后,就会生成可执行文件,这个可执行文件也是ELF类型,是另外一种格式。这个我们暂时不用深究,知道就行,这个就是加载进程需要的程序了(后续会用到)。
4、我们要知道,Linux创建进程不是从0直接创建的,而是通过一个父进程调用fork()复制一个相同的进程的形式来创建子进程的。
这就类似于设计模式里面的原型模式了,从0到有比较费事,还是cv来得快
fork()函数的介绍如下:
pid_t fork( void);
(pid_t 是一个宏定义,其实质是int 被定义在#includesys/types.h>中)
返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1
使用fork函数的实例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
extern int create_process (char* program, char** arg_list);
int create_process (char* program, char** arg_list)
{
pid_t child_pid;
child_pid = fork ();
if(child_pid == -1) {
//出错了
}
if (child_pid != 0)
// 父进程
return child_pid;
else {
// 新创建的子进程,可以执行自己的逻辑
execvp (program, arg_list);
abort ();
}
}
5、创建完子进程,则需要自己调用程序代码了。这就像我们执行了 ./test.sh param可执行脚本后,操作系统要做的逻辑。
- 接收执行的参数,如上面的param。此时还在用户态, 类似方法的传参
- 开始进入内核态,执行系统调用,加载程序,分别调用了sys_execve、do_execve、load_elf_binary。注意第三步load_elf_binary,它的操作就是载入我们前面链接生成的elf文件,载入elf文件,进程就知道他需要干什么了。
看到这里,你不妨回过头去看看第一张图上的流程,对照一下。
拓展
Linux操作系统一开始有一个0号进程(idle进程),它是操作系统启动时的第一个进程。没有它就没有后面的其他进程。在源码里面,它是通过set_task_stack_end_magic(&init_task),这里面有一个参数 init_task,定义是 struct task_struct init_task = INIT_TASK(init_task)创建的。
有了0号进程,才fork出了1号进程(用户态进程的父进程),2号进程(内核态进程的父进程)。
下面我们选一台机子,ps -ef看一下:
我们看到1号进程和2号进程的父进程是0号进程,下面几个进程父进程都是2号进程,都是内核进程。在cmd一列也可以看到,带中括号的是内核进程,不带中括号的是用户态进程。
上一篇: C#设计模式之12-代理模式