# linux-进程-01-进程和fork

linux有三座大山:进程,内存,io。跨过了这三座大山,你就能看到更精彩的世界! 今天开始总结进程。

# linux源码

linux源码在/usr/src下,主要目录的用途:

include/ 建立内核代码时所需要的大部分包含文件
init/ 内核的初始化代码
arch/ 所有硬件结构的内核代码
drivers/ 所有设备驱动程序
fs/ 所有的文件系统代码
net/ 内核中关于网络的代码
mm/ 内存管理的代码
ipc/ 进程间通信的代码
kernel/ 内核的主体代码

# 进程和线程

# 进程是什么

在linux中,进程是资源的分配单位,它关注的是资源方面的内容,如:内存,文件系统,打开的文件,信号等。linux是通过task_struct来管理进程,该结构体定义在include/linux/sched.h文件中,包含的主要内容有:

pid;   // 进程的pid
*mm;  // 内存资源
*fs;  // 文件系统
*files;  // 文件资源,如打开的文件
*signal; // 信号
...

task_struct.png 当我们说到进程时,就是说一个包含了内存,句柄文件,pid等的东西。假设linux的内存是一个蛋糕,则某个进程占用的内存就是在这个蛋糕上切下的一小块。

# 线程是什么

从资源的角度来说,线程是粒度比进程更细的单位。同一个进程中,可能会有多个线程,它们共享资源。

怎么理解呢?

假设有两个程序A和B,当linux执行A时,会先加载A的上下文,然后执行A,完成后保存A的上下文,然后加载B的上下文,执行B。

假设A由a,b,c三个模块组成,则在进行A时,会执行a,b,c,这些模块的执行不需要上下文切换,共享A的上下文,即共享A的资源。

A的整个执行过程为:A获得CPU -> CPU加载A的上下文 -> 执行a -> 执行b -> 执行c -> CPU保存A的上下文 。

A和B就是进程,a,b,c就是线程

在linux中,所有符合task_struct结构体的程序,都可以被调度运行。 在后面的表述中,不会去纠结叫线程还是进程,关注task_struct结构体即可。

# pid

每个进程都会有一个pid。linux的pid数量是有限的,具体数量在/proc/sys/kernel/pid_max中。

[root@localhost ~]# cat /proc/sys/kernel/pid_max 
32768

当某个进程结束后,该pid会回收,之后分配给新的进程。

pid的分配规则如下:

1). 进程pid分配范围为(300, pid_max),这是由于pid<300的情况值允许分配一次。
2). 每个pid分配成功,便会把当前的pid设置到last_pid, 那么下次pid的分配便是从last_pid+1开始往下查找。也就是说pid的分配是从300开始累加的,比如当前最大pid为2035,则下一个进程获得的pid为2036。
3). 当pid分配完最大值时,则从之前回收的最小pid分配,如此循环利用。

如果所有的pid都分配完了,没有可用的pid时会怎么样呢?答案是无法创建新的进程,以及你可能要重启系统^_^。

最典型的一个列子是fork炸弹:

:(){:|:&};:

// 冒号表示一个函数,你可以用其他任意字符代替它,这个函数的内容是调用自己,即递归调用,&是将该函数放到后台执行,分号结束后,使用冒号执行该函数。

在linux执行这段命令,不一会儿系统就会崩溃。它的原理就是不断地fork新进程,将系统的pid耗尽。

# fork

在linux中,有个fork函数,它的作用是创建一个与原来进程几乎完全相同的进程。为什么说是几乎呢,因为fork之后,它们可能会执行不同的代码。

先看个简单的fork例子:

main()
{
	fork();
	printf("hello\n");
	fork();	
	printf("hello\n");
	while(1);
}

运行这段代码,你会看到最终打印了6个hello。

第一次fork时,产生了两个进程,所以打印两次hello,之后第二次fork,每个进程又产生一个,最终有四个进程,所以第二次打印时,会看到打印了4个hello。

如何做到fork后,这两个进程做不同的事情呢,一个简单的例子如下:

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
	pid_t pid,wait_pid;
	int status;

	pid = fork();

	if (pid==-1)	{
		perror("Cannot create new process");
		exit(1);
	} else 	if (pid==0) {
		printf("a\n");
	} else {
		printf("b\n");
	}

	printf("c\n");
	while(1);
}

运行这段代码,你会看到屏幕会打印ac,bc,原因是fork有如下特性:

1). 在父进程中,fork返回新创建子进程的进程ID;
2). 在子进程中,fork返回0;
3). 如果出现错误,fork返回一个负值;

利用fork的返回值,我们就可以做不同的事情了。

# 小结

本次主要总结了:

  1. 进程的定义
  2. task_struct结构体
  3. pid
  4. fork

顺带说了下linux源码的位置即个目录用途,方便阅读查找。

下次我们看下进程的生命周期。