内核进程基础
进程定义
操作系统作为硬件的使用层,提供使用硬件资源的能力;进程作为操作系统的使用层,提供使用操作系统抽象出的资源层的能力。
进程:是指计算机中已运行的程序。进程本身不是基本的运行单位,而是线程的容器。程序是指令、数据及其组织形式的描述,进程才是程序(指令和数据)的真正运行实例。
Linux内核把进程叫做task(任务),进程的虚拟地址空间可分为用户虚拟地址空间和内核虚拟地址空间,所有进程共享内核虚拟地址空间(内核页表全局唯一),每个进程有独立的用户虚拟地址空间。
进程有两种特殊的形式:没有用户虚拟地址空间的进程叫内核线程;共享用户虚拟地址空间的进程叫用户线程。共享同一个用户虚拟地址空间的所有用户线程叫线程组。
linux arm64架构进程虚拟地址空间布局:
- <font style="color:rgb(64, 64, 64);">用户空间,低地址(地址范围 </font>`**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">0x0000_0000_0000_0000 - 0x0000_FFFF_FFFF_FFFF</font>**`<font style="color:rgb(64, 64, 64);">)。</font>
- <font style="color:rgb(64, 64, 64);">内核空间,高地址(地址范围 </font>`**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">0xFFFF_0000_0000_0000 - 0xFFFF_FFFF_FFFF_FFFF</font>**`<font style="color:rgb(64, 64, 64);">)。</font>
Linux进程四要素
- 有一段程序供其执行;
- 有进程专用的系统堆栈空间;
- 在内核有
**task_struct**
数据结构(进程描述符); - 有独立的存储空间,拥有专有的用户空间;
如果有前三条而缺少第四条,则称为线程;如果完全没有用户空间,则称为内核线程;如果共享用户空间则称为用户线程。
进程描述符task_struct
linux中每个进程都应该一个进程描述符task_struct
。
内核为每个进程分配一个task_struct结构体;实际分配两个连续物理页面(8192字节);
struct task_struct {volatile long state; // 进程的状态标志void *stack; // 指向内核栈,每个进程都有一个内核栈/*下面这4个是进程调度策略和优先级*/int prio;int static_prio;int normal_prio;unsigned int rt_priority;const struct sched_class *sched_class; // 表示该进程所属的调度器类int nr_cpus_allowed;const cpumask_t *cpus_ptr; // 允许进程在哪些CPU上运行cpumask_t cpus_mask;// 这两个指针指向内存描述符。进程:mm和active_mm指向同一个内存描述符。内核线程:mm是空指针,// 当内核线程运行时,active_mm指向从进程借用内存描述符struct mm_struct *mm; // 指向内存描述符; mm设置为NULL,则为内核线程struct mm_struct *active_mm;pid_t pid; // 全局的进程号pid_t tgid; // 全局的线程组标识符/* Real parent process: */struct task_struct __rcu *real_parent; // 指向真实的父进程/* Recipient of SIGCHLD, wait4() reports: */struct task_struct __rcu *parent; // 指向父进程;如果进程被另一个进程使用系统调用跟踪,那么父进程是跟踪进程;// 否则是real_parentstruct task_struct *group_leader; // 指向线程组的组长struct hlist_node pid_links[PIDTYPE_MAX]; // 进程号,进程组标识符和会话标识符/* Objective and real subjective task credentials (COW): */const struct cred __rcu *real_cred; // 指向主体和真实课题证书/* Effective (overridable) subjective task credentials (COW): */const struct cred __rcu *cred; // 指向有效客体证书char comm[TASK_COMM_LEN]; // 进程名称// 下面这两个成员用于UNIX系统:信号量和共享内存
#ifdef CONFIG_SYSVIPCstruct sysv_sem sysvsem;struct sysv_shm sysvshm;
#endif...
}
进程创建方法
在Linux内核中,新进程是从一个已经存在的进程中复制出来的;内核使用静态数据结构构造出0号内核线程,0号内核线程分叉生成1号内核线程和2号内核线程(kthreadd线程)。1号内核线程完成初始化后装载用户程序,变成1号进程,其他进程都是1号进程或者它的子孙进程分叉生成的;其他内核线程是kthreadd线程分叉生成的。
linux提供了2个系统调用(fork
和clone
),都可以用来创建新的进程。fork
是最常用的方式。fork
创建的新进程是原进程的副本,采用了写时复制技术。
fork
在内核中将工作委托给_do_fork
,其中主要的工作是copy_process
。
当前进程执行其它程序的方法
在Linux中,**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">fork()</font>**
和 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">exec()</font>**
是进程创建和程序执行的核心系统调用,通常结合使用以实现新进程的启动(如Shell运行命令)。
fork()复制当前进程(父进程),生成一个几乎完全相同的子进程,使用了写时复制(COW)。
在子进程中通过<font style="color:rgb(64, 64, 64);">exec()</font>
执行新程序。加载新程序到当前进程空间,替换原有代码、数据、堆栈等。
下面是一个通过<font style="color:rgb(64, 64, 64);">fork</font>
+<font style="color:rgb(64, 64, 64);">exec</font>
,实现在当前进程中执行<font style="color:rgb(64, 64, 64);">ls</font>
命令的测试代码。
#include <stdio.h> // printf, perror
#include <unistd.h> // fork, execl
#include <sys/types.h> // pid_t
#include <sys/wait.h> // waitint main() {pid_t pid;// 创建子进程pid = fork();if (pid < 0) {// fork 失败perror("fork failed");return 1;}if (pid == 0) {// 子进程printf("Child process (PID: %d) is running `ls -l`...\n", getpid());// 使用 execl 执行 /bin/ls 程序,参数依次是:// 第一个参数:程序路径// 第二个参数:argv[0],约定俗成写程序名(可随意,但最好与实际命令一致)// 后续参数:命令行参数(可多个),以 NULL 结尾execl("/bin/ls", "ls", "-l", NULL);// 如果 exec 成功,上面那句就会替换当前进程,下面的不会执行perror("exec failed"); // exec 调用失败return 1;} else {// 父进程printf("Parent process (PID: %d), waiting for child (PID: %d)...\n", getpid(), pid);// 等待子进程结束int status;waitpid(pid, &status, 0); // 也可以用 wait(&status)if (WIFEXITED(status)) {printf("Child exited with status %d\n", WEXITSTATUS(status));} else {printf("Child terminated abnormally\n");}}return 0;
}
参考资料
- Professional Linux Kernel Architecture,Wolfgang Mauerer
- Linux内核深度解析,余华兵
- Linux设备驱动开发详解,宋宝华
- linux kernel 4.12