操作系统中进程的实现

简介: // 进程控制结构体(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会执行现场恢复, 执行进程函数, 退出进程
目录
相关文章
|
23天前
|
算法 调度 UED
深入理解操作系统:进程调度与优先级队列
【10月更文挑战第31天】在计算机科学的广阔天地中,操作系统扮演着枢纽的角色,它不仅管理着硬件资源,还为应用程序提供了运行的环境。本文将深入浅出地探讨操作系统的核心概念之一——进程调度,以及如何通过优先级队列来优化资源分配。我们将从基础理论出发,逐步过渡到实际应用,最终以代码示例巩固知识点,旨在为读者揭开操作系统高效管理的神秘面纱。
|
16天前
|
消息中间件 安全 算法
深入理解操作系统:进程管理的艺术
【10月更文挑战第38天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是硬件与软件的桥梁,更是维持计算机运行秩序的守夜人。本文将带你走进操作系统的核心——进程管理,探索它是如何协调和优化资源的使用,确保系统的稳定与高效。我们将从进程的基本概念出发,逐步深入到进程调度、同步与通信,最后探讨进程安全的重要性。通过这篇文章,你将获得对操作系统进程管理的全新认识,为你的计算机科学之旅增添一份深刻的理解。
|
20天前
|
算法 调度 UED
深入理解操作系统:进程管理与调度策略
【10月更文挑战第34天】本文旨在探讨操作系统中至关重要的一环——进程管理及其调度策略。我们将从基础概念入手,逐步揭示进程的生命周期、状态转换以及调度算法的核心原理。文章将通过浅显易懂的语言和具体实例,引导读者理解操作系统如何高效地管理和调度进程,保证系统资源的合理分配和利用。无论你是初学者还是有一定经验的开发者,这篇文章都能为你提供新的视角和深入的理解。
40 3
|
22天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
23天前
|
算法 调度 UED
深入理解操作系统的进程调度机制
本文旨在探讨操作系统中至关重要的组成部分之一——进程调度机制。通过详细解析进程调度的概念、目的、类型以及实现方式,本文为读者提供了一个全面了解操作系统如何高效管理进程资源的视角。此外,文章还简要介绍了几种常见的进程调度算法,并分析了它们的优缺点,旨在帮助读者更好地理解操作系统内部的复杂性及其对系统性能的影响。
|
24天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
37 2
|
24天前
|
消息中间件 算法 Linux
深入理解操作系统之进程管理
【10月更文挑战第30天】在数字时代的浪潮中,操作系统作为计算机系统的核心,扮演着至关重要的角色。本文将深入浅出地探讨操作系统中的进程管理机制,从进程的概念入手,逐步解析进程的创建、调度、同步与通信等关键过程,并通过实际代码示例,揭示这些理论在Linux系统中的应用。文章旨在为读者提供一扇窥探操作系统深层工作机制的窗口,同时激发对计算科学深层次理解的兴趣和思考。
|
25天前
|
消息中间件 算法 调度
深入理解操作系统:进程管理与调度策略
【10月更文挑战第29天】本文将带领读者深入探讨操作系统中的核心组件之一——进程,并分析进程管理的重要性。我们将从进程的生命周期入手,逐步揭示进程状态转换、进程调度算法以及优先级调度等关键概念。通过理论讲解与代码演示相结合的方式,本文旨在为读者提供对进程调度机制的全面理解,从而帮助读者更好地掌握操作系统的精髓。
32 1
|
25天前
|
算法 调度 UED
深入理解操作系统中的进程调度
【10月更文挑战第29天】探索进程调度的奥秘,本文将带你深入了解在操作系统中如何管理和控制多个并发执行的程序。从简单的调度算法到复杂的多级反馈队列,我们将逐步揭示如何优化系统性能和提高资源利用率。准备好一起揭开进程调度的神秘面纱吧!
|
26天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!