/*
* linux/kernel/fork.c
*
* (C) 1991 Linus Torvalds
*/
/*
注意:signal.c和fork.c文件的编译选项内不能有vc变量优化选项/Og,因为这两个文件
	内的函数参数内包含了函数返回地址等内容。如果加了/Og选项,编译器就会在认为
	这些参数不再使用后占用该内存,导致函数返回时出错。
	math/math_emulate.c照理也应该这样,不过好像它没有把eip等参数优化掉:)
*/
#include <set_seg.h>

/*
* 'fork.c'中含有系统调用'fork'的辅助子程序(参见system_call.s),以及一些其它函数
* ('verify_area')。一旦你了解了fork,就会发现它是非常简单的,但内存管理却有些难度。
* 参见'mm/mm.c'中的'copy_page_tables()'。
*/
#include <errno.h>		// 错误号头文件。包含系统中各种出错号。(Linus 从minix 中引进的)。

#include <linux/sched.h>	// 调度程序头文件,定义了任务结构task_struct、初始任务0 的数据,
// 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。
#include <linux/kernel.h>	// 内核头文件。含有一些内核常用函数的原形定义。
#include <asm/segment.h>	// 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。
#include <asm/system.h>		// 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。

extern void write_verify (unsigned long address);

long last_pid = 0;

//// 进程空间区域写前验证函数。
// 对当前进程的地址addr 到addr+size 这一段进程空间以页为单位执行写操作前的检测操作。
// 若页面是只读的,则执行共享检验和复制页面操作(写时复制)。
void verify_area (void *addr, int size)
{
	unsigned long start;

	start = (unsigned long) addr;
// 将起始地址start 调整为其所在页的左边界开始位置,同时相应地调整验证区域大小。
// 此时start 是当前进程空间中的线性地址。
	size += start & 0xfff;
	start &= 0xfffff000;
	start += get_base (current->ldt[2]);// 此时start 变成系统整个线性空间中的地址位置。
	while (size > 0)
	{
		size -= 4096;
// 写页面验证。若页面不可写,则复制页面。(mm/memory.c,261 行)
		write_verify (start);
		start += 4096;
	}
}

// 设置新任务的代码和数据段基址、限长并复制页表。
// 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)	// 0.11 版不支持代码和数据段分立的情况。
		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;	// 新基址=任务号*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;
}

/*
* OK,下面是主要的fork 子程序。它复制系统进程信息(task[n])并且设置必要的寄存器。
* 它还整个地复制数据段。
*/
// 复制进程的进程管理结构
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;
	struct i387_struct *p_i387;   //协处理器数据结构

	//进程管理结构需要一个页面存储,页面申请在主内存的最末位置
	p = (struct task_struct *) get_free_page ();	// 为新任务数据结构分配内存。
	if (!p)			// 如果内存分配出错,则返回出错码并退出。
		return -EAGAIN;
	task[nr] = p;			// 将新任务结构指针放入任务数组中。//实现挂接
// 其中nr 为任务号,由前面find_empty_process()返回。
	*p = *current;		/* NOTE! this doesn't copy the supervisor stack */
/* 注意!这样做不会复制超级用户的堆栈 (只复制当前进程内容)。*/
	p->state = TASK_UNINTERRUPTIBLE;	// 将新进程的状态先置为不可中断等待状态。
	p->pid = last_pid;		// 新进程号。由前面调用find_empty_process()得到。
	p->father = current->pid;	// 设置父进程号。
	p->counter = p->priority;
	p->signal = 0;		// 信号位图置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;	// 当前滴答数时间。
// 以下设置任务状态段TSS 所需的数据(参见列表后说明)。
	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p;	// 堆栈指针(由于是给任务结构p 分配了1 页
// 新内存,所以此时esp0 正好指向该页顶端)。
	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;	// 段寄存器仅16 位有效。
	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);	// 该新任务nr 的局部描述符表选择符(LDT 的描述符在GDT 中)。
	p->tss.trace_bitmap = 0x80000000;
// 如果当前任务使用了协处理器,就保存其上下文。
	p_i387 = &p->tss.i387;
	if (last_task_used_math == current)
	_asm{
		mov ebx, p_i387
		clts
		fnsave [p_i387]
	}
//    __asm__ ("clts ; fnsave %0"::"m" (p->tss.i387));
// 设置新任务的代码和数据段基址、限长并复制页表。如果出错(返回值不是0),则复位任务数组中
// 相应项并释放为该新任务分配的内存页。
	if (copy_mem (nr, p))
	{				// 返回不为0 表示出错。
		task[nr] = NULL;
		free_page ((long) p);
		return -EAGAIN;
	}
// 如果父进程中有文件是打开的,则将对应文件的打开次数增1。
	for (i = 0; i < NR_OPEN; i++)
		if (f = p->filp[i])
			f->f_count++;
// 将当前进程(父进程)的pwd, root 和executable 引用次数均增1。
	if (current->pwd)
		current->pwd->i_count++;
	if (current->root)
		current->root->i_count++;
	if (current->executable)
		current->executable->i_count++;
// 在GDT 中设置新任务的TSS 和LDT 描述符项,数据从task 结构中取。
// 在任务切换时,任务寄存器tr 由CPU 自动加载。

//这里实现的是进程1的tss和ldt与全局描述符表进行挂接操作
	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;		// 返回新进程号(与任务号是不同的)。
}

// 为新进程取得不重复的进程号last_pid,并返回在任务数组中的任务号(数组index)。
int find_empty_process (void)
{
	int i;

repeat:
	if ((++last_pid) < 0)
		last_pid = 1;
	for (i = 0; i < NR_TASKS; i++)
		if (task[i] && task[i]->pid == last_pid)
			goto repeat;
	for (i = 1; i < NR_TASKS; i++)	// 任务0 排除在外。
		if (!task[i])
			return i;
	return -EAGAIN;
}

Linux0.11 创建进程的过程分析--fork函数的使用的更多相关文章

  1. Linux0.11之进程0创建进程1(1)

    进程0是由linus写在操作系统文件中的,是预先写死了的.那么进程0以后的进程是如何创建的呢?本篇文章主要讲述进程0创建进程1的过程. 在创建之前,操作系统先是进行了一系列的初始化,分别为设备号.块号 ...

  2. 对Linux0.11 中 进程0 和 进程1分析

    1. 背景 进程的创建过程无疑是最重要的操作系统处理过程之一,很多书和教材上说的最多的还是一些原理的部分,忽略了很多细节.比如,子进程复制父进程所拥有的资源,或者子进程和父进程共享相同的物理页面,拥有 ...

  3. 进程控制之fork函数

    一个现有进程可以调用fork函数创建一个新进程. #include <unistd.h> pid_t fork( void ); 返回值:子进程中返回0,父进程中返回子进程ID,出错返回- ...

  4. 【Linux编程】进程标识符与fork函数

    ID为0的进程一般是调度进程.常被称为交换进程(swapper),是内核中的系统进程. ID为1的进程叫做init进程,是一个普通用户进程,不属于内核,由内核调用. 一个现有进程能够调用fork函数创 ...

  5. 进程管理之fork函数

    fork函数的定义 #include <unistd.h> #include <sys/types.h> pid_t fork(void); fork函数在父进程中返回子进程的 ...

  6. UNIX环境编程学习笔记(19)——进程管理之fork 函数的深入学习

    lienhua342014-10-07 在“进程控制三部曲”中,我们学习到了 fork 是三部曲的第一部,用于创建一个新进程.但是关于 fork 的更深入的一些的东西我们还没有涉及到,例如,fork ...

  7. 从创建进程到进入main函数,发生了什么?

    前几天,读者群里有小伙伴提问:从进程创建后,到底是怎么进入我写的main函数的? 今天这篇文章就来聊聊这个话题. 首先先划定一下这个问题的讨论范围:C/C++语言 这篇文章主要讨论的是操作系统层面上对 ...

  8. Linux -- 进程管理之 fork() 函数

    一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. Test1 f ...

  9. linux0.11内核源码——进程各状态切换的跟踪

    准备工作 1.进程的状态有五种:新建(N),就绪或等待(J),睡眠或阻塞(W),运行(R),退出(E),其实还有个僵尸进程,这里先忽略 2.编写一个样本程序process.c,里面实现了一个函数 /* ...

随机推荐

  1. mongo索引

    索引自动创建和手工创建 db.stu.drop(); db.stu.insert({"name":"张三","sex":"男&qu ...

  2. spring cloud 入门系列四:使用Hystrix 实现断路器进行服务容错保护

    在微服务中,我们将系统拆分为很多个服务单元,各单元之间通过服务注册和订阅消费的方式进行相互依赖.但是如果有一些服务出现问题了会怎么样? 比如说有三个服务(ABC),A调用B,B调用C.由于网络延迟或C ...

  3. geotrellis使用(四十)优雅的处理请求超过最大层级数据

    前言 要说清楚这个题目对我来说可能都不是一件简单的事情,我简单尝试. 研究 GIS 的人应该都清楚在 GIS 中最常用的技术是瓦片技术,无论是传统的栅格瓦片还是比较新颖的矢量瓦片,一旦将数据切好瓦片就 ...

  4. 使用Boost program_options控制程序输入

    简介 很多人使用界面来输入数据,本文的程序介绍如何使用Boost的program_options控制输入. 程序中使用了: 1. 短选项 2. 可以指定多个参数的选项 程序 #include < ...

  5. hbase大规模数据写入的优化历程

    业务背景:由于需要将ngix日志过滤出来的1亿+条用户行为记录存入Hbase数据库,以此根据一定的条件来提供近实时查询,比如根据用户id及一定的时间段等条件来过滤符合要求的若干行为记录,满足这一场景的 ...

  6. 安卓框架——SlidingMenu使用技巧

    SlidingMenu的一些常用属性 原文转载http://blog.csdn.net/zwl5670/article/details/48274109 [java] view plain copy ...

  7. SpringMVC常用配置(二),最简洁的配置实现文件上传

    Spring.SpringMVC持续介绍中,基础配置前面已经介绍了很多,如果小伙伴们还不熟悉可以参考这几篇文章: 1.Spring基础配置 2.Spring常用配置 3.Spring常用配置(二) 4 ...

  8. 使用C++将OpenCV中Mat的数据写入二进制文件,用Matlab读出

    在使用OpenCV开发程序时,如果想查看矩阵数据,比较费劲,而matlab查看数据很方便,有一种方法,是matlab和c++混合编程,可以用matlab访问c++的内存,可惜我不会这种方式,所以我就把 ...

  9. Android fragment(片段)构建灵活的UI

    在以支持多种屏幕尺寸为目标设计应用时,您可以在不同的布局配置中重复使用您的fragment 从而根据可用的屏幕空间优化用户体验. 例如,在手机设备上,由于采用单窗格用户界面,因此可能更适合一次只显示一 ...

  10. [Mysql]Innodb 独立表空间和共享表空间

    innodb有2中表空间方式: 共享表空间 和 独立表空间 查询数据的设置: show variables like '%per_table'; 默认是共享表空间,独立表空间在配置文件中添加 inno ...