进程管理
进程作为用户操作的实体,它贯穿操作系统的整个生命周期,而程序是由若干段二进制代码组成的。进程可以说是程序的运行态抽象,即运行于处理器中的二进制码叫做进程,保存在存储介质中的二进制码叫做程序。进程会在执行过程中引入运行环境维护信息,因此进程管理啊主要涉及两个部分内容:进程控制结构体和进程间调度策略。
进程控制结构体用于记录和手机进程运行时的资源消耗信息,并维护程序运行的现场环境;进程间调度策略主要负责决策一个进程将在何时能获得处理器的执行权。
简述进程管理模块
进程作为拥有执行资源的最小单位,他为每个程序维护着运行时的各种资源,比如进程ID、进程的页表、进程执行现场的寄存器值、进程各个段地址空间分布信息以及进程执行时的维护信息等,他们在程序运行期间会被经常或实时更新。这些资源有组织地被结构化到PCB(Process Controller Block,进程控制结构体)内,PCB作为进程调度的决策信息供进程调度算法使用。系统内核的所有组成部分皆是为进程高效、稳定的运行提供服务。
进程调度策略负责将满足运行条件或迫切需要执行的进程调配到空闲处理器中执行。进程调度策略是操作系统中非常重要的一部分,它直接影响程序的执行效率,即使操作系统拥有再强大的处理器,也很可能被糟糕的调度策略拖后腿,甚至拖垮。
CR0-CR3寄存器
CR0中含有控制处理器操作模式和状态的系统控制标志;
CR1保留不用;
CR2含有导致页错误的线性地址;
CR3中含有页目录表物理内存基地址,因此该寄存器也被称为页目录基地址寄存器PDBR(Page-Directory Base addressRegister)。
PCB Process Controller Block 进程控制结构体
用于记录进程的资源使用情况(包括硬件资源和软件资源)和运行态信息等。
struct task_struct
{
struct List list;
volatile long state;
unsigned long flags;
struct mm_struct *mm;
struct thread_struct *thread;
unsigned long addr_limit;
long pid;
long counter;
long signal;
long priority;
}
struct mm_struct
{
pml4t_t * pgd;
unsigned long start_code,end_code;
unsigned long start_data,end_data;
unsigned long start_rodata,end_data;
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;
unsigned long gs;
unsigned long cr2;
unsigned long trap_nr;
unsigned long error_code;
}
借鉴了linux内核的设计思想,即把进程控制结构体struct task_struct与进程的内核层栈空间融为一体。其中,地地址存放struct task_struct 结构体,而余下的地址高地址空间则作为进程的内核层栈空间使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-15hUgB26-1636897268417)(C:\Users\86187\AppData\Roaming\Typora\typora-user-images\image-20211026225432957.png)]
ld链接脚本
每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情.
https://blog.csdn.net/wtbcx2012/article/details/45508113
借助一个联合体,把进程控制结构体struct task_struct与进程的内核层栈空间连续到了一起,其中的宏常量STACK_SIZE被定义为32768B(32KB),他表示进程的内核栈空间和struct task_struct结构体占用的存储空间总量为32KB。
再将union task_union实例化成全局变量init_task_union,并将其作为操作系统的第一个进程。进程控制结构体数组init_task(指针数组)是为了处理器创建的初始进程控制结构体,目前只有数组的第0个元素已投入使用。
拥有进程后如何切换进程?
进程间的切换过程必须要在一块公共区域内进行,故此块区域往往由内核空间提供。这个暗示着进程的应用层空间有应用程序自身维护,从而使得进程可运行格子的程序。而内核空间则用于处理所有进程对系统的操作请求,这其中就包括进程间的切换,因此内核层空间是所有进程共享的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqEZtpmt-1636897268419)(C:\Users\86187\AppData\Roaming\Typora\typora-user-images\image-20211028000931552.png)]
prev进程通过调用switch_to模块来保存RSP寄存器的当前值,并指定切换回prev进程时RIP寄存器值,此处默认将其指定在标识符1:处。随后,将next进程的栈指针恢复到RSP寄存器中,再把next进程执行现场的RIP寄存器压入next进程的内核层栈空间(RSP寄存器的恢复前,此后的数据将压入next进程的内核层栈空间)。
其实处理器早就在运行第一个进程中了,只不过此前的进程控制结构体尚未初始化完毕。全局变量中的_stack_start记录着系统第一个进程内核层栈基地址。
通常情况下,系统的第一个进程会协助操作系统完成一些初始化任务,该进程会在执行完初始化任务后进入等待阶段,他会在系统没有可运行进程时休眠处理器以达到省电的目的,因此第一个进程不存在应用层空间。对于这个没用应用层空间的进程而言,其init_mm结构体变量(struct mm_struct结构体)保存的不再是应用程序信息,而是内核程序的各个段信息以及内核层栈基地址。
接下来,为系统创建第二个进程,这个进程常被称作"init进程"。对于调用kernel_thread函数时传入的CLONE_FS、CLONE_FILES、CLONE_SIGNAL等克隆标志位,预留使用。
init函数与主函数一样,经过编译器的编译生成若干个程序片段并记录程序的入口地址,当操作系统为进程创建进程控制结构体时,操作系统会去的程序的入口地址,并从这个入口地址处执行。从编程角度来看,进程是由一系列维护程序运行的信息和若干组片段构成的。
inline void __switch_to(struct task_struct *prev,struct task_struct *next)
{
init_tss[].rsp0 = next->thread->rsp0;
set_tss64(init_tss[0].rsp0,init_rss[0].rsp1,init_tss[0].rsp2,init_tss[0].ist1,init_tss[0].ist2,init_tss[0].ist3,init_tss[0].ist4,init_tss[o].ist5,init_tss[0].ist6,init_tss[0].ist7);
__asm__ __volatile__(
"movq %%fs, %0 \n\t:"=a"(prev->thread->fs)"
);
__asm__ __volatile__(
"movq %%gs, %0 \n\t":"=a"(prev->thread->gs)
);
__asm__ __volatile__("movq %0, %%fs \n\t"::"a"(next->thread->fs));
__asm__ __volatile__("movq %0, %%gs \n\t"::"a"(next->thread->gs));
color_printk(WHITE,BLACK,"prev->thread->rsp0:%#0181x\n",prev->thread->rsp0);
color_printk(WHITE,BLACK,"next->thread->rsp0:%#0181x\n",next->thread->rsp0);
}
void task_init()
{
struct task_struct *p = NULL;
init_mm.pgd = (pml4t_t *) Global_cr3;
init_mm.start_code = memory_management_struct.start_code;
init_mm.end_code = memory_management_struct.end_code;
init_mm.start_data = (unsigned long)&_data;
init_mm.end_data = memory_management_struct.end_code;
init_mm.start_rodata = (unsigned long)&_rodata;
init_mm.end_rodata = (unsigned long)&_erodata;
init_mm.start_brk = 0;
init_mm.end_brk = memory_management_struct.end_brak;
init_mm.start_stack = _starck_start;
set_tss64(init_tss[0].rsp0,init_rss[0].rsp1,init_tss[0].rsp2,init_tss[0].ist1,init_tss[0].ist2,init_tss[0].ist3,init_tss[0].ist4,init_tss[o].ist5,init_tss[0].ist6,init_tss[0].ist7);
init_tss[0].rsp0 = init_thread.rsp0;
list_init(&init_task_union.task.list);
kernel_thread(init,10,CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
init_task_union.task.state = TASK_RUNNING;
p = container_of(list_next(¤t->list),struct task_struct,list);
__switch_to(current,p);
}
unsigned long init(unsigned long arg)
{
color_printk(RED,BLACK,"init task is running ,arg:%#0181x\n",arg);
}
int kernel_thread(unsigned long (* fn)(unsigned long),unsigned long args , unsigned long flags)
{
struct pt_regs regs ;
memset(®s , 0 , sizeof(regs));
regs.rbx = (unsigned long )fn;
regs.rdx = (unsigned long)args;
regs.ds = KERNEL_DS;
regs.es = KERNEL_DS;
regs.cs = KERNEL_CS;
regs.ss = KERNEL_DS;
regs.rflags = (1 << 9);
regs.rip = (unsigned long )kernel_thread_func;
return do_fork($regs,flags , 0 , 0);
}
unsigned long do_fork(struct pt_regs * regs, unsigned long clone_flags,unsigned long stack_start,unsigned long stack_size)
{
struct task_struct *tsk = NULL;
struct thread_struct *thd = NULL;
struct Page *p = NULL;
color_printk(WHITE,BLACK,"alloc_pages,bitmap:%#0181x\n",*memory_management_struct.bits_map);
p = alloc_pages(ZONE_NORMAL,1,PG_PTable_Maped | PG_Active | PG_Kernel);
tsk = (struct task_struct *)Pht_To_Virt(p->PHY_address);
memset(tsk,0,sizeof(*tsk));
*tsk = *current;
list_init(&tsk->list);
list_add_to_before(&init_task_union.task.list,&tsk->list);
tsk->pid ++;
tsk->state = TASK_NINTERRUPTEIBLE;
thd = (struct thread_struct *)(tsk + 1);
tsk ->thread = thd;
memcpy(regs,(void *)((unsigned long)tsk +STACK_SIZE - sizeof(sturct pt_regs)),sizeof(struct pt_regs));
thd->rsp0 = (unsigned long)tsk + STACK_SIZE;
thd->rip = regs->rip;
thd->rsp = (unsigned long)tsk + STACK_SIZE - sizeof(struct pt_regs);
if(!(tsk->flags & PF_KTHREAD))
thd->rip = regs->rip = (unsigned long)ret_from_intr;
tsk->state = TASK_RUNNING;
reutrn 0;
}
extern void kernel_thread_func(void);
__asm__ (
"kernel_thread_func: \n\t"
" popq %r15 \n\t"
" popq %r14 \n\t"
" popq %r13 \n\t"
" popq %r12 \n\t"
" popq %r11 \n\t"
" popq %r10 \n\t"
" popq %r9 \n\t"
" popq %r8 \n\t"
" popq %rbx \n\t"
" popq %rcx \n\t"
" popq %rdx \n\t"
" popq %rsi \n\t"
" popq %rdi \n\t"
" popq %rbp \n\t"
" popq %rax \n\t"
" movq %rax, %ds \n\t"
" popq %rax \n\t"
" movq %rax, %es \n\t"
" popq %rax \n\t"
" addq $0x38, %rsp \n\t"
" movq %rax, %rdi \n\t"
" callq *%rbx \n\t"
" movq %rax, %rdi \n\t"
" callq %rax, %rdi \n\t"
" callq do_exit \n\t"
)
unsigned long do_exit(unsigned long code)
{
color_printk(RED,BLACK,"exit task is running,arg:%#0181x\n",code);
}
do_fork才是创建进程的核心函数,而kernel_thread函数更像是对创建出的进程做了特殊限制,这个由kernel_thread函数创建出来的进程更像是一个线程。尽管kernel_thread函数借助do_fork函数创建出了进程控制结构体,但是这个进程却没有应用层空间。其实,kernel_thread函数只能创建出没有应用层空间的进程,如果有诸多这样的进程同时运行在内核中,他们看起来更像是内核 主进程创建出的若干个线程一般,因此常被叫做内核线程。综上,kernel_thread的功能是创建内核线程,所以init此时是个内核级线程,但不会一直是个内核线程。当执行do_execve函数后,他会转变为一个用户级线程
|