操作系统中进程的实现

简介: // 进程控制结构体(PCB) --> 用来管理进程struct tack_struct { struct List list; // 双向链表, 用于连接各个进程控制结构体, 在Linux中这样的链表创建方式比较常见 volatile long state; // 表示进程的状态...
// 进程控制结构体(PCB) --> 用来管理进程
struct tack_struct {
    struct List list; // 双向链表, 用于连接各个进程控制结构体, 在Linux中这样的链表创建方式比较常见
    volatile long state; // 表示进程的状态: 运行态, 停止态, 可中断态等
    unsigned long flags; // 进程标志, 是进程还是线程, 也许这就是Linux中的线程被称为轻量级的进程的原因

    struct mm_struct *mm; // 记录内存页表和程序段信息, 说白了就是管理内存中的程序(data, code, rodata, bss), 应用程序的栈顶地址
    struct thread_struct *thread; // 用于保存进程切换时的数据

    unsigned long addr_limit; // 进程地址空间范围

    long pid; 
    long counter; // 进程占用的时间片
    long signal; // 进程的信号
    long priority; // 进程的优先级
 
};


struct mm_struct {
    // pgd的值是从cr3寄存器中获取的, 就是页表的地址
    pml4t_5 *pgd; // 页表指针

    unsigned long start_code, end_code;
    unsigned long start_data, end_data;
    unsigned long start_rodata, end_rodata;
    unsigned long start_brk, end_brk;
    // 应用程序的栈顶地址
    unsigned long start_stack;
};


// 用于保留现场
struct thread_struct {
    unsigned long rsp0; // 内核层栈基地址

    unsigned long rip; // 内核层代码指针
    unsigned long rsp; // 内核层当前栈指针

    unsigned long fs; // 存储fs段寄存器的值
    unsigned long gs; // gs段寄存器的值

    unsigned long cr2; // cr2控制寄存器的值
    unsigned long trap_nr; // 产成异常的异常号
    unsigned long error_code; // 异常的错误码
};

/*
 * 在Linux内核中, 将task_truct结构体和进程的内核层栈空间融为一体, 低地址存放task_struct结构体, 余下的存放进程的内核层栈空间使用
 *
 */

// 通过该联合体创建出来的是Linux下第一个进程, 注意: 这个进程不是我们提到的init进程, init进程是Linux第二个进程
union task_union {
    struct task_struct task;
    unsigned long stack[STACK_SIZE / sizeof(unsigned long)];
};


#define INIT_TASK(tsk) \
{\
    .state = TASK_UNINTERRUPTIBLE, \
    .flags = PF_KTHREAD,\
    .mm = &init_mm,\
    .thread = &init_thread, \
    .addr_limit = 0xffff800000000000, \
    .pid = 0, \
    .counter = 1, \
    .signal = 0, \
    .priority = 0\
}

// 1, 2, 3都是初始化第一个进程
// 1
union task_union init_task_union __attribute__((__section__(".data.init_task"))) = {INIT_TASK(init_task_union.task)};

// 2
struct task_struct *init_task[NP_CPUS] = {&init_task_union.task, 0};
struct mm_struct init_mm = {0};

// 3
struct thread_struct init_thread = {
    .rsp0 = (unsigned long)(init_task_union.stack + STACK_SIZE / sizeof(unsigned long)),
    .rsp = (unsigned long)(init_task_union.stack + STACK_SIZE / sizeof(unsigned long)),
    .fs = KERNEL_DS,
    .gs = KERNEL_DS,
    .cr2 = 0,
    .trap_nr = 0,
    .error_code = 0
};


struct tss_struct {
    unsigned int reserved0;
    unsigned long rsp0;
    unsigned long rsp1;
    unsigned long rsp2;
    unsigned long reserved1;
    unsigned long ist1;
    unsigned long ist2;
    unsigned long ist3;
    unsigned long ist4;
    unsigned long ist5;
    unsigned long ist6;
    unsigned long ist7;
    unsigned long reserved2;
    unsigned short reserved3;
    unsigned short iomapbaseaddr;
}__attirbute__((packed));

#define INIT_TSS \
{\
    .reserved = 0,
    ... \
    .ist1 = 0xffff800000007c00,\
    ...\
}

struct tss_struct init_tss[NR_CPUS] = { [0 ... NR_CPUS - 1] = INIT_TSS };
/*
 * 在之前我们恢复异常和中断的现场的时候就发现要对大量的寄存器中的数据压栈保存, 有在task_struct的thread在进程切换的时候保存数据的方式的启发, 我们可以定义一个结构体来保存保留
 * 现场的寄存器的数据, 然后直接将这个结构体中的数据直接拷贝到内核栈中, 而不是一个一个地压入到内核栈中, 这样效率高
 */

/*
 * 这个进程的现场保留的数据就交给了这个pt_regs结构体了, 他和异常以及中断时将大部分寄存器中的数据压栈是一样的
 */

/*
 * 我们知道一个操作系统会管理很多个进程, 我们需要有一个函数来获取当前的task_struct
 */

inline struct task_struct *get_current() {
    struct task_struct *current = NULL;
    __asm__ __volatile__("andq %%rsp, %0":"=r"(current):"0"(~3276UL));
    return current;
}


#define current get_current()

#define GET_CURRENT \
    "movq %rsp, %rbx \n\t" \
    "andq $ - 32768, %rbx \n\t"
  • 进程内的切换是在内核空间中的, 如果将这个机制搬运到应用程序中则实现了线程间的切换工作
  • 进程间的切换主要涉及到页目录的切换和各个寄存器值的保存和恢复
  • 进程间切换需要在一块公共区域内进行, 这个区域就是内核空间(注意: 作为的在内核空间运行就是指我们当前的堆栈指针指向的是内核的堆栈)
  • 对于操作系统的第一个PCB我们进行特殊的创建, 就是直接手动的创建, 目前来看这个就是我们的current的进程控制结构体了, 接着我们要生成一个新的进程, 这个进程就是我们熟悉的init进程, 这个进程和剩余的其他进程都是通过一个kernel_thread函数创建的, 而在kernel_thread函数中核心的创建子函数就是do_fork函数, 这个函数会拷贝当前的current的内存数据到一个新的区域, 很熟悉吧, 这个就是所谓的子进程从父进程中的一个复制, 下面就是来介绍这个kernel_thread函数
  • kernel_thread:
    • 先声明一点, 所谓创建一个新的进程和一个老的进程恢复现场都是一个样的, 所以我们在创建一个新的进程的时候采用伪造恢复现场的方式来创建
    • 因此在kernel_thread函数中, 我们先创建一个用来存储恢复现场的数据结构体, 也就是pt_regs struct结构体, 这个结构体的属性几乎就是所有的寄存器(因为保留现场我们需要将几乎所有的寄存器的值都保存起来), 这个pt_regs结构体在前面有提到过, 这里还有一点需要注意: 我们在这里还要提到一个kernel_thread_func函数指针(在这个函数中先进行现场恢复, 再调用传入的进程的入口函数地址使用call指令执行该进程代码, 接着返回, 调用call do_exit退出进程程序, 如果不在内核层的话, 则调用的是另外一个ret_from_syscall, 因为do_fork是一个系统调用), 这个函数就是用来在执行我们的进程的function的使用先执行的一段恢复现场的代码, 这里我们又提到了一个function, 其实这个就是一个程序的入口地址而已, 我们所以的进程不就是一个动态的程序吗, 就是让cpu去执行, 那么就需要知道他的入口地址
    • 初始化完毕pt_regs结构体之后, 使用的do_fork函数去fork父进程创建出子进程
    • do_fork 函数
      + 在该函数中我们需要调用分页的alloc_pages函数去一个物理页存放task truct(也就是进程控制结构体)
      + 接着讲task所在的物理页的数据清0
      + 这个语句 task = current就是核心了, 就是所谓的子进程从父进程复制数据出来(这里没有使用先进的CoW技术)
      + 将这个task结构体添加到进程控制列表中
      + 递增pid, 原来pid只这样出来的 :)
      + thread结构体就创建在task之后, thread结构体是用来存储一些进程的状态的, 用于现场保留什么的
      + 判断task是在内核态还是在应用层
      + 设置task的state为TASK_RUNNING
  • 调用了kernel_thread函数之后, 我们只是完成了一个子进程的创建, 现在我们要去运行该进程了
  • 使用switch_to函数进行进程切换即可
    • 在switch_to函数中, 会执行current进程的现场保留
    • 修改rip等寄存器的值, 让其指向新的子进程的func函数指针指向的地址, 在上面我们已经知道了func会执行现场恢复, 执行进程函数, 退出进程
目录
相关文章
|
7天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
29 1
|
17天前
|
算法 调度 Python
深入理解操作系统中的进程调度算法
在操作系统中,进程调度是核心任务之一,它决定了哪个进程将获得CPU的使用权。本文通过浅显易懂的语言和生动的比喻,带领读者了解进程调度算法的重要性及其工作原理,同时提供代码示例帮助理解。
|
11天前
|
调度 开发者 Python
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
|
9天前
|
C语言 开发者 内存技术
探索操作系统核心:从进程管理到内存分配
本文将深入探讨操作系统的两大核心功能——进程管理和内存分配。通过直观的代码示例,我们将了解如何在操作系统中实现这些基本功能,以及它们如何影响系统性能和稳定性。文章旨在为读者提供一个清晰的操作系统内部工作机制视角,同时强调理解和掌握这些概念对于任何软件开发人员的重要性。
|
9天前
|
Linux 调度 C语言
深入理解操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅,从进程管理的基本概念出发,逐步探索到内存管理的高级技巧。我们将通过实际代码示例,揭示操作系统如何高效地调度和优化资源,确保系统稳定运行。无论你是初学者还是有一定基础的开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。
|
10天前
|
存储 算法 调度
深入理解操作系统:进程调度的奥秘
在数字世界的心脏跳动着的是操作系统,它如同一个无形的指挥官,协调着每一个程序和进程。本文将揭开操作系统中进程调度的神秘面纱,带你领略时间片轮转、优先级调度等策略背后的智慧。从理论到实践,我们将一起探索如何通过代码示例来模拟简单的进程调度,从而更深刻地理解这一核心机制。准备好跟随我的步伐,一起走进操作系统的世界吧!
|
9天前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
10天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
13天前
|
算法 Linux 调度
深入浅出操作系统的进程管理
本文通过浅显易懂的语言,向读者介绍了操作系统中一个核心概念——进程管理。我们将从进程的定义出发,逐步深入到进程的创建、调度、同步以及终止等关键环节,并穿插代码示例来直观展示进程管理的实现。文章旨在帮助初学者构建起对操作系统进程管理机制的初步认识,同时为有一定基础的读者提供温故知新的契机。
|
13天前
|
消息中间件 算法 调度
深入理解操作系统之进程管理
本文旨在通过深入浅出的方式,带领读者探索操作系统中的核心概念——进程管理。我们将从进程的定义和重要性出发,逐步解析进程状态、进程调度、以及进程同步与通信等关键知识点。文章将结合具体代码示例,帮助读者构建起对进程管理机制的全面认识,并在实践中加深理解。