从linux0.11看一个进程的诞生

2024-03-27 21:32
文章标签 进程 诞生 linux0.11

本文主要是介绍从linux0.11看一个进程的诞生,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

系统有一个GDT表。该表保存了系统和所有进程的tss和ldt描述符信息。tss就是我们平时说的进程上下文。每个进程有一个ldt数组,里面保存了代码段和数据段的描述符信息。
首先,从一个进程的诞生说起。我们知道,通过fork可以创建一个进程。下面我们来看一下fork的过程都做了什么事情。先通过find_empty_process获取一个可用的进程id和pcb。pid是进程id。pcb是管理进程的结构体。

int find_empty_process(void)
{int i;repeat:// 先找到一个可用的pidif ((++last_pid)<0) last_pid=1;for(i=0 ; i<NR_TASKS ; i++)if (task[i] && task[i]->pid == last_pid) goto repeat;// 再找一个可用的pcb项,从1开始,0是init进程for(i=1 ; i<NR_TASKS ; i++)if (!task[i])return i;return -EAGAIN;
}

接着调用copy_process复制父进程的信息。

/**  Ok, this is the main fork-routine. It copies the system process* information (task[nr]) and sets up the necessary registers. It* also copies the data segment in it's entirety.*/
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,long ebx,long ecx,long edx,long fs,long es,long ds,long eip,long cs,long eflags,long esp,long ss)
{struct task_struct *p;int i;struct file *f;// 申请一页存pcbp = (struct task_struct *) get_free_page();if (!p)return -EAGAIN;// 挂载到全局pcb数组task[nr] = p;// 复制当前进程的数据*p = *current;	/* NOTE! this doesn't copy the supervisor stack */p->state = TASK_UNINTERRUPTIBLE;p->pid = last_pid;p->father = current->pid;p->counter = p->priority;p->signal = 0;p->alarm = 0;p->leader = 0;		/* process leadership doesn't inherit */p->utime = p->stime = 0;p->cutime = p->cstime = 0;// 当前时间p->start_time = jiffies;p->tss.back_link = 0;// 页末p->tss.esp0 = PAGE_SIZE + (long) p;p->tss.ss0 = 0x10;// 调用fork时压入栈的ip,子进程创建完成会从这开始执行,即if (__res >= 0) p->tss.eip = eip;p->tss.eflags = eflags;// 子进程从fork返回的是0,eax会赋值给__resp->tss.eax = 0;p->tss.ecx = ecx;p->tss.edx = edx;p->tss.ebx = ebx;p->tss.esp = esp;p->tss.ebp = ebp;p->tss.esi = esi;p->tss.edi = edi;// 段选择子是16位p->tss.es = es & 0xffff;p->tss.cs = cs & 0xffff;p->tss.ss = ss & 0xffff;p->tss.ds = ds & 0xffff;p->tss.fs = fs & 0xffff;p->tss.gs = gs & 0xffff;/*计算第nr进程在GDT中关于LDT的索引,切换任务的时候,这个索引会被加载到ldt寄存器,cpu会自动根据ldt的值,把GDT中相应位置的段描述符加载到ldt寄存器(共16+32+16位)*/p->tss.ldt = _LDT(nr); p->tss.trace_bitmap = 0x80000000;if (last_task_used_math == current)__asm__("clts ; fnsave %0"::"m" (p->tss.i387));/*设置线性地址范围,挂载线性地址首地址和限长到ldt,赋值页目录项和页表执行进程的时候,tss选择子被加载到tss寄存器,然后把tss里的上下文也加载到对应的寄存器,比如cr3,ldt选择子。tss信息中的ldt索引首先从gdt找到进程ldt结构体数据的首地址,然后根据当前段的属性,比如代码段,则从cs中取得选择子,系统从ldt表中取得进程线性空间的首地址、限长、权限等信息。用线性地址的首地址加上ip中的偏移,得到线性地址,然后再通过页目录和页表得到物理地址,物理地址还没有分配则进行缺页异常等处理。*/if (copy_mem(nr,p)) {task[nr] = NULL;free_page((long) p);return -EAGAIN;}// 父子进程都有同样的文件描述符,file结构体加一for (i=0; i<NR_OPEN;i++)if (f=p->filp[i])f->f_count++;// inode节点加一if (current->pwd)current->pwd->i_count++;if (current->root)current->root->i_count++;if (current->executable)current->executable->i_count++;/*挂载tss和ldt地址到gdt,nr << 1即乘以2,这里算出的是第nr个进程距离第一个tss描述符地址的偏移,单位是8个字节,即选择描述符大小,_LDT是偏移的大小,单位是1,这里是8*/set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));p->state = TASK_RUNNING;	/* do this last, just in case */return last_pid;
}int copy_mem(int nr,struct task_struct * p)
{unsigned long old_data_base,new_data_base,data_limit;unsigned long old_code_base,new_code_base,code_limit;// 代码段限长code_limit=get_limit(0x0f);// 数据段限长data_limit=get_limit(0x17);old_code_base = get_base(current->ldt[1]);old_data_base = get_base(current->ldt[2]);if (old_data_base != old_code_base)panic("We don't support separate I&D");if (data_limit < code_limit)panic("Bad data_limit");// 设置进程的线性地址的首地址,每个进程占64MBnew_data_base = new_code_base = nr * 0x4000000;p->start_code = new_code_base;// 设置线性地址到ldt的描述符中set_base(p->ldt[1],new_code_base);set_base(p->ldt[2],new_data_base);// 把父进程的页目录项和页表复制到子进程,old_data_base,new_data_base是线性地址,父子进程共享物理页面,即copy on writeif (copy_page_tables(old_data_base,new_data_base,data_limit)) {free_page_tables(new_data_base,data_limit);return -ENOMEM;}return 0;
}

下面具体分析一下几个地方。
1 _LDT的宏展开如下:

/** Entry into gdt where to find first TSS. 0-nul, 1-cs, 2-ds, 3-syscall* 4-TSS0, 5-LDT0, 6-TSS1 etc ...*/
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
// 第一个tss选择子的偏移是4<<3,4乘以8,等于32,即从GDT的偏移为32开始算,第一个进程的n是0,tss是32
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
// 第一个ldt选择子的偏移是5<<3,5乘以8,等于40,即从GDT的偏移为40开始算,第一个进程的n是0,ldt是40
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))

_LDT是计算出进程ldt在GDT表的索引。

2

// 设置线性地址到ldt的描述符中set_base(p->ldt[1],new_code_base);set_base(p->ldt[2],new_data_base)

3

/*挂载tss和ldt地址到gdt,nr << 1即乘以2,这里算出的是第nr个进程距离第一个tss描述符地址的偏移,单位是8个字节,即选择描述符大小,_LDT是偏移的大小,单位是1,这里是8*/set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));

设置完后的结构
在这里插入图片描述
进程创建的本质就是申请一个新的pcb,里面保存了该进程的相关信息,假设现在轮到该进程执行。系统会根据tss选择子到gdt表中找到tss结构体的地址。然后使用tss结构体的内容恢复执行上下文。然后找到tss中的ldt选择子,把ldt选择子加载到ldtr寄存器,然后根据ldt选择子到gdt表中可以找到对应的ldt描述符。根据cs:ip的值。cs寄存器里存的是代码段的选择子。是0x17。即ldt的第二项,和数据段一样。从ldt第二项中找出基地址和限长。基地址+ip得到线性地址的值。然后再根据页目录和页表就能得到物理值。

这篇关于从linux0.11看一个进程的诞生的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/853450

相关文章

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

Windows的CMD窗口如何查看并杀死nginx进程

《Windows的CMD窗口如何查看并杀死nginx进程》:本文主要介绍Windows的CMD窗口如何查看并杀死nginx进程问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Windows的CMD窗口查看并杀死nginx进程开启nginx查看nginx进程停止nginx服务

Java进程CPU使用率过高排查步骤详细讲解

《Java进程CPU使用率过高排查步骤详细讲解》:本文主要介绍Java进程CPU使用率过高排查的相关资料,针对Java进程CPU使用率高的问题,我们可以遵循以下步骤进行排查和优化,文中通过代码介绍... 目录前言一、初步定位问题1.1 确认进程状态1.2 确定Java进程ID1.3 快速生成线程堆栈二、分析

Python多进程、多线程、协程典型示例解析(最新推荐)

《Python多进程、多线程、协程典型示例解析(最新推荐)》:本文主要介绍Python多进程、多线程、协程典型示例解析(最新推荐),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 目录一、multiprocessing(多进程)1. 模块简介2. 案例详解:并行计算平方和3. 实现逻

C#通过进程调用外部应用的实现示例

《C#通过进程调用外部应用的实现示例》本文主要介绍了C#通过进程调用外部应用的实现示例,以WINFORM应用程序为例,在C#应用程序中调用PYTHON程序,具有一定的参考价值,感兴趣的可以了解一下... 目录窗口程序类进程信息类 系统设置类 以WINFORM应用程序为例,在C#应用程序中调用python程序

Python如何精准判断某个进程是否在运行

《Python如何精准判断某个进程是否在运行》这篇文章主要为大家详细介绍了Python如何精准判断某个进程是否在运行,本文为大家整理了3种方法并进行了对比,有需要的小伙伴可以跟随小编一起学习一下... 目录一、为什么需要判断进程是否存在二、方法1:用psutil库(推荐)三、方法2:用os.system调用

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序