内核进程基础

进程定义

操作系统作为硬件的使用层,提供使用硬件资源的能力;进程作为操作系统的使用层,提供使用操作系统抽象出的资源层的能力。

进程:是指计算机中已运行的程序。进程本身不是基本的运行单位,而是线程的容器。程序是指令、数据及其组织形式的描述,进程才是程序(指令和数据)的真正运行实例。

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_parent

	struct 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_SYSVIPC
	struct sysv_sem			sysvsem;
	struct sysv_shm			sysvshm;
#endif

    ...
}

进程创建方法

在Linux内核中,新进程是从一个已经存在的进程中复制出来的;内核使用静态数据结构构造出0号内核线程,0号内核线程分叉生成1号内核线程和2号内核线程(kthreadd线程)。1号内核线程完成初始化后装载用户程序,变成1号进程,其他进程都是1号进程或者它的子孙进程分叉生成的;其他内核线程是kthreadd线程分叉生成的。

linux提供了2个系统调用(forkclone),都可以用来创建新的进程。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>   // wait

int 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;
}

参考资料

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer
  2. Linux内核深度解析,余华兵
  3. Linux设备驱动开发详解,宋宝华
  4. linux kernel 4.12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值