# 进程和程序

学号265，原创作品转载请注明出处 + <https://github.com/mengning/linuxkernel/>

### 1.task\_struct结构体

linux内核中对进程信息的管理存放在task\_stuct结构体中，结构体中主要有标识符、状态、优先级、进程相关的指针等关键信息，下面对这些主要内容进行分析。

#### 1.1 进程状态：

```c
volatile long state;
```

state就是描述进程状态的成员，其可能的取值和对应的状态在sched.h中有具体的定义，总的来说其值<0代表不在运行状态，=0代表正在运行，>0代表停止状态。

#### 1.2 进程标识符：

```c
pid_t pid;  //进程标识符
pid_t tgid;  //线程组标识符
```

#### 1.3 进程相关指针：

```c
struct task_struct __rcu *real_parent; //父进程指针
struct task_struct __rcu *parent; //父进程指针

struct list_head children;	//子进程链表指针
struct list_head sibling;	//指向父进程的子进程链表的指针
struct task_struct *group_leader;	//指向进程组的首进程
```

#### 1.4 优先级：

```c
int prio, static_prio, normal_prio;
unsigned int rt_priority;
```

prio保存动态优先级，static\_prio保存静态优先级，normal\_prio的值取决于静态优先级和调度策略，rt\_priority保存实时优先级。

#### 1.5 调度策略

```c
unsigned int policy;
```

调度策略在内核中主要有五种，其可能取值的定义在include/uapi/linux/sched.h中。

### 2.do\_fork和进程创建

fork，vfork，clone三者的实现函数都指向了do\_fork，区别只是传入的参数不同，do\_fork函数声明如下：

```c
long do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr)
```

其函数体主要执行流程如下：

* 调用copy\_process为子进程复制出一份进程信息；
* 调用wake\_up\_new\_task将子进程加入调度器，为之分配cpu；

在copy\_process中创建新的task\_struct指针，然后复制当前进程的task\_struct，再对其中的一些值进行修改。

### 3.gdb跟踪fork

首先更新Linuxkernel文件夹中的menu，然后将test\_fork.c覆盖test.c，再修改Makefile文件，使得虚拟机运行后先stop，以便使用gdb进行调试。

然后打开新的终端，打开gdb对qemu中的系统程序进行调试，在sys\_clone，do\_fork，copy\_process三个函数设置断点：

![](https://1465371034-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-L_hNHHksYYsMvQNKcN8%2F-Latfh4r5bjhpnU2p6_T%2F-LathDLbvVKvuNhuuOUf%2F32.png?alt=media\&token=523e7c79-9b56-417a-913c-544d8e367e03)

然后输入c进行程序跟踪，跟踪过程如下：

![](https://1465371034-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-L_hNHHksYYsMvQNKcN8%2F-Latfh4r5bjhpnU2p6_T%2F-LathNfeOnpo94AK7tvd%2F33.png?alt=media\&token=55589f3f-b337-489d-9ecb-5c870bebc722)

可以看到fork确实如上面分析的流程，首先调用系统函数sys\_clone，然后sys\_clone调用do\_fork，do\_fork接着调用copy\_process。

### 4.编译和链接

#### 4.1 编译的过程：

编译需要经过**预处理**、**编译**、**汇编**、**链接**四个过程：

* 预处理：主要处理宏定义、条件编译指令、头文件包含指令，预处理完成的基本上是对源程序的替代工作；
* 编译：将代码转换为汇编代码；
* 汇编：将汇编代码转为机器码，在Linux上一般为ELF目标文件；
* 链接：将生成的目标文件与系统库文件进行链接，最终生成可执行文件。

#### 4.2 链接：

链接分为**静态链接**、**载入时动态链接**、**运行时动态链接：**

* 静态链接：在编译链接阶段就将需要库函数和目标文件合并生成可执行文件，该方法生成的可执行文件实行速度快，但可能会导致可执行文件过大；
* 载入时动态链接：在编译前已知需要调用到哪些库，编译时在目标文件中保留所需的链接信息，程序执行时通过链接信息加载对应代码到程序执行空间中；
* 运行时动态链接：编译前不知道会调用哪些库，当程序运行时需要调用到某些库时，使用特定API来加载所需库。

### 5.进程调度的时机

进程调度的时机一般为：

* 进程执行结束；
* 当前进程的时间片用完；
* 发生中断、异常；

因此schedule()函数的调用时机一般也应该在这些情况被调用，schedule()函数的功能是选择下一个进程进执行。
