Linux0.11 copy_process()详解

今天主要解决几个操作系统学习过程中的问题。

  1. get_free_page()的代码
  2. copy_page_table()的代码
    上个星期杨老师已经详细讲解了copy_process()这个函数,它是一个重要的函数,所以我们先来复习一下。
    由于我们现在要练基本功,我就直接粘贴linux0.11所有的代码,一定要严谨,以便串联起所有的内容。
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;

	p = (struct task_struct *) get_free_page();//这里是第一次调用get_free_page
	if (!p)
		return -EAGAIN;
	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;
	p->tss.eip = eip;
	p->tss.eflags = eflags;
	p->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;
	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;
	p->tss.ldt = _LDT(nr);
	p->tss.trace_bitmap = 0x80000000;
	if (last_task_used_math == current)
		__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
	if (copy_mem(nr,p)) {
		task[nr] = NULL;
		free_page((long) p);
		return -EAGAIN;
	}
	for (i=0; i<NR_OPEN;i++)
		if (f=p->filp[i])
			f->f_count++;
	if (current->pwd)
		current->pwd->i_count++;
	if (current->root)
		current->root->i_count++;
	if (current->executable)
		current->executable->i_count++;
	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;
}
  • 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.*

好吧,上面是Linus大神自己写的注释,也就是说这是一个主要的进程复制的作用,它复制了task[nr]的内容和一些必要的寄存器,并且也复制了数据段里的全部内容。
问题1:为什么要进行进程复制?
换句话说,在编写第一代OS的时候为什么要进行copy_process,而不是creat_process(),这是一个创新性的原因!
首先,可以想到这样创建进程是简单的,不过每个进程是不一样的,之后也要进行必要的修改;
然后,可以想到进程间是需要进行共享数据的,那么这个共享的数据是什么呢?
最后,我们来从代码实际运行的角度来看的话,新进程创建之后是什么都没有的,甚至连代码都在磁盘里面,那么怎么实现从0到1呢,现在就可以复用父进程的代码将子进程的代码从磁盘拷贝到内存当中进行运行。
问题2:参数是如何传进来的
这里需要去看调用copy_process()的汇编代码。
我好懒呀,之后去写~
问题3:变量代表的意义
struct task_struct *p : 这是一个task_struct类型的指针,指向现在的子进程。
对于进程的运行来说要做几件大事:
1.将进程指针挂到task数组当中,这也是在copy_process()代码中首先做的【操作系统代码的重要思想之一是先做大事】。
…还有几件大事来着,我忘记了555~,等会去问问。
*current :这也是一个task_struct类型的指针,指向父进程。

union task_union {
	struct task_struct task;
	char stack[PAGE_SIZE];
};

这里是一个重要的union,它为一个进程分配了一个页的空间,其中包含了一个task_struct结构,剩下的是这个进程的内核栈。当copy_process()中执行*p=*current时,是不复制父进程的内核栈的。
last_pid :这是一个进程号,记录了从这一次操作系统启动之后所有的进程号,只增不减的。

get_free_page()

p = (struct task_struct *) get_free_page();//这里是第一次调用get_free_page(),下面我们来分析具体代码⑧。

/*
 * Get physical address of first (actually last :-) free page, and mark it
 * used. If no free pages left, return 0.
 */
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");

__asm__("std ; repne ; scasb\n\t"
	"jne 1f\n\t"
	"movb $1,1(%%edi)\n\t"
	"sall $12,%%ecx\n\t"
	"addl %2,%%ecx\n\t"
	"movl %%ecx,%%edx\n\t"
	"movl $1024,%%ecx\n\t"
	"leal 4092(%%edx),%%edi\n\t"
	"rep ; stosl\n\t"
	"movl %%edx,%%eax\n"
	"1:"
	:"=a" (__res)
	:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
	"D" (mem_map+PAGING_PAGES-1)
	:"di","cx","dx");
return __res;
}

这是在memory.c中的一段嵌入式汇编代码。
整体框架:__asm__(汇编代码:输出寄存器:输入寄存器:不变寄存器);
输入:%1(ax=0);%2(LOW_MEM);%3(cx=PAGING PAGES); %4(edi=mem_map+PAGING_PAGES-1)
输出:%0(ax=页面起始地址)
std;//置方向标志位
repne; 当ECX!=0并且ZF==0时 重复执行后边的指令 每执行一次ECX的值减1。repne是重复执行指令。
scas是用来搜索字符。
一些标志位参数:
DF:方向
CX:所要搜索的串的长度
ax:所要搜索的数据
di:所要搜索的那条串串
也就是要搜索mem_map,从后往前搜索,如果遇到标志为0的则继续执行,否则都被占用了,直接返回0表示没找到空页。
sall $12,%%ecx :将算术左移12位,也就是cx(页面数)*4K=相对页面起始位置
addl %2,%%ecx:加上一个低内存地址,获得实际页面起始位置
movl %%ecx,%%edx:将页面起始位置放到edx寄存器中
movl $1024,%%ecx:ecx置为1024
leal 4092(%%edx),%%edi:装入有效地址4096(4K)+edx(实际起始地址)=该页的末尾地址放到edi中
rep ; stosl:将edi指向的内存清零
movl %%edx,%%eax:返回页面起始位置
至此为止,这个获得空页的函数就看完啦~~

copy_mem()

我们再次回到copy_process()这个函数中,发现有一堆设置值的操作,设置完了之后有一个copy_mem(nr,p),也就是复制内存【操作系统代码的重要思想之一是函数起名见名知义】现在我们进入这个函数看一看

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");
	new_data_base = new_code_base = nr * 0x4000000;    //对齐  64个*64MB
	p->start_code = new_code_base;
	set_base(p->ldt[1],new_code_base);
	set_base(p->ldt[2],new_data_base);
	if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
		free_page_tables(new_data_base,data_limit);
		return -ENOMEM;
	}
	return 0;
}

new_data_base = new_code_base = nr * 0x4000000; 这句话需要解释一下,0x4000000=64MB,这个数字要有一定的敏感度,我们知道一个页目录表项对应1K个页表,一个页表对应4K的空间,那么一个页目录表项对应的就是4MB的空间,一个进程使用了16个页目录表项,就是64MB。
接下来看到这一句
code_limit=get_limit(0x0f);

get_limit()

#define get_limit(segment) ({ \
unsigned long __limit; \
__asm__("lsll %1,%0\n\tincl %0"
:"=r" (__limit)
:"r" (segment)); \
__limit;})

#endif

lsll 是加载段界限的指令,把 segment 段描述符中的段界限字段装入__limit,函数返回__limit 加 1,即段长。

接下里看到这一句copy_page_tables(old_data_base,new_data_base,data_limit)
我们进入copy_page_table()看一下

copy_page_table()

先看代码

int copy_page_tables(unsigned long from,unsigned long to,long size)
{
	unsigned long * from_page_table;
	unsigned long * to_page_table;
	unsigned long this_page;
	unsigned long * from_dir, * to_dir;
	unsigned long nr;

	if ((from&0x3fffff) || (to&0x3fffff))//一个页目录表的管辖范围,页目录表对齐
		panic("copy_page_tables called with wrong alignment");
	from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 *///4M之下对齐 页目录项对齐
	to_dir = (unsigned long *) ((to>>20) & 0xffc);
	size = ((unsigned) (size+0x3fffff)) >> 22; //看看你多少个页目录表项
	for( ; size-->0 ; from_dir++,to_dir++) {
		if (1 & *to_dir) //1是P位,存在位  这个已经有东西了,没创建的时候就有了
			panic("copy_page_tables: already exist");
		if (!(1 & *from_dir)) //父进程的这个没有
			continue;
		from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
		if (!(to_page_table = (unsigned long *) get_free_page()))
			return -1;	/* Out of memory, see freeing */
		*to_dir = ((unsigned long) to_page_table) | 7;
		nr = (from==0)?0xA0:1024;//如果父进程是0,则是160项即640K
		for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
			this_page = *from_page_table;
			if (!(1 & this_page))
				continue;
			this_page &= ~2;//只读 自适应字长;写底层软件的时候尤其需要注意
			*to_page_table = this_page;
			if (this_page > LOW_MEM) {
				*from_page_table = this_page;
				this_page -= LOW_MEM;
				this_page >>= 12;
				mem_map[this_page]++;
			}
		}
	}
	invalidate();//刷TLB 
	return 0;
}

if ((from&0x3fffff) || (to&0x3fffff)):为什么要进行这个判断呢?0x3fffff就是22个1,22位对应的是4M,也就是说from和to(老的数据基址和新的数据基址)的低22位都必须是0,即必须是4M(一个页目录表项的管辖范围)对齐的
from_dir = (unsigned long *) ((from>>20) & 0xffc); //from_dir4M之下对齐 页目录项对齐。
这里面是两个for循环,把旧数据段的页表项复制到新的数据段的页表,页目录表是不需要复制的,只需要一个页目录表就行了,因为所有的进程都在同一个线性地址空间当中,一个页目录表就对应一个线性地址空间。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux0.11中,进程是通过进程控制块(Process Control Block,PCB)来描述的。每个进程都有一个唯一的进程标识符(Process ID,PID),PCB 中存储了该进程的各种状态信息,包括进程状态、程序计数器、堆栈指针、资源占用情况等。 Linux0.11采用了基于时间片的轮转调度算法,即每个进程被分配一个时间片,当时间片用完后,调度器会把正在运行的进程挂起,把CPU分配给下一个就绪进程。在进程的状态转换中,可发生以下三种情况: 1. 就绪:进程已经准备好运行,但尚未获得CPU。 2. 运行:进程正在运行,占用CPU。 3. 阻塞:进程因为等待某些事件(如I/O操作)而被阻塞。 当一个进程被创建时,它会被加入到就绪队列中,等待CPU分配。当进程被阻塞时,它会被移动到阻塞队列中,等待事件的发生。当事件发生后,进程会被移回就绪队列,等待CPU分配。当进程的时间片用完时,它会被移回就绪队列,等待CPU分配。 在Linux0.11中,进程的调度是在时钟中断处理程序中完成的。当时钟中断发生时,调度器会根据就绪队列的优先级和时间片情况,选择下一个要运行的进程,并将CPU分配给它。如果当前进程的时间片还没有用完,它会继续运行,直到时间片用完或者被阻塞。如果当前进程被阻塞,调度器会选择下一个就绪进程运行。 总之,Linux0.11采用了简单而高效的轮转调度算法,能够满足基本的进程管理需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神仙诙谐代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值