一 内核空间和用户空间
内核:操作系统(OS)中最核心的部分,负责管理和分配硬件资源。主要由进程管理和调度,内存管理,文件系统和IO等几个主要部分组成。
内核子系统只工作在内核空间(kernel-space),而一般的用户进程只工作在用户空间(User-space)。
内核空间比用户空间拥有更多的执行权限。
比如armv8架构的cpu处理器支持最少EL0/EL1/EL3三个特权级别,EL0权限最小,可访问的系统资源最少,EL3权限最大,相对应可以访问和管理的系统资源也更多。对于运行在armv8架构上的linux系统,用户空间对应EL0;内核空间对应EL1。
当然这也意味着内核编码承担了更多的风险。如果内核程序出现问题,极有可能出现系统的崩溃(例如,系统panic)。而用户进程出现问题,最多就是该进程被kill(例如Segment Fault)。
系统调用(System Call)是用户空间和内核空间交互的接口。用户进程通过系统调用使用内核提供的服务和资源,例如,创建进程,分配内存,打开文件IO等等。系统调用一般依赖cpu的支持,例如对于armv8架构的cpu,使用专门的指令SVC执行系统调用,从用户空间进入内核空间。
二 进程管理
2.1 进程的概念
程序不是进程,进程是处于执行期的程序及其所使用的资源的总称。
所以,常说进程是资源管理的单位。
当然,一个进程可能会有一个或多个线程,这些线程共享这个进程中的所有资源。
对于Linux内核而言,内核管理的就是一个个线程,也称做任务(task)。对于拥有多线程的进程,可以看作一个线程组。
2.2 进程描述符(PCB)
内核如何描述一个进程呢?
通常,内核需要给进程分配地址空间,也需要记录该进程打开的文件信息,还要存放该进程的父进程的信息,进程的状态,进程的Pid等等。
因此,linux内核使用一个结构体来描述一个进程:
每创建一个任务,Linux都会分配一个struct task_struct结构体实例,这个结构体实例就代表了这个任务。
例如,内核在创建一个进程时,会给内存分配运行的空间,这些内存信息都在mm字段中描述。一个进程中的多个线程是如何共享同一片进程空间的呢?其实是因为这些线程的mm指向都指向同一个struct mm_struct结构体,从而达到了进程地址空间共享的目的。(好比一个家庭共享房子,车子等等)。
eg.1
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int fd;
fd = open("./test.txt", O_WRONLY | O_CREAT);
while (1) {
sleep(1);
printf("hello process!\n");
}
}
运行以上程序,我们可以看到该进程打开了4个文件,0~4:
root@yanghua-VirtualBox:/proc/8461/fd# ls -l
total 0
lrwx------ 1 root root 64 5月 4 18:05 0 -> /dev/pts/1
lrwx------ 1 root root 64 5月 4 18:05 1 -> /dev/pts/1
lrwx------ 1 root root 64 5月 4 18:05 2 -> /dev/pts/1
l-wx------ 1 root root 64 5月 4 18:05 3 -> /home/yanghua/codebase/linux_test/test.txt
在一个系统里面,能够同时存在的进程个数是有限制的:
root@yanghua-VirtualBox:/home/yanghua/codebase/linux_test# cat /proc/sys/kernel/pid_max
32768
因此,实际pid的范围也是1~32768;当系统进程数超过32768时,将无法继续创建新的进程。
2.3 进程管理方式
2.3.1 任务列表(链表管理)
2.3.2 父子关系(进程树的管理)
task_struct 结构体中有指向父进程的指针:
也有管理自己的子进程的链表: