欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux进程的创建过程

程序员文章站 2022-06-13 14:02:33
...

进程

进程的产生极大地提高的cpu的利用率,进程是cpu的执行单元。cpu可以通过进程切换提高使用率。那么Linux的进程是如何创建的呢?
这里一副图,我们先看个大概:
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看一下:
Linux进程的创建过程
我们看到1号进程和2号进程的父进程是0号进程,下面几个进程父进程都是2号进程,都是内核进程。在cmd一列也可以看到,带中括号的是内核进程,不带中括号的是用户态进程