#include <errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <asm/system.h>
申明外部调用函数,验证地址所指向的页面是否可写
extern void write_verify(unsigned long address);
全局变量,用于产生可用的进程id
long last_pid=0;
对指定起始地址和长度进行验证
void verify_area(void * addr,int size)
{
unsigned long start;
start = (unsigned long) addr;
计算给定地址的页内其实地址:start & 0xfff
计算区域边界地址
size += start & 0xfff;
计算给定地址的页面起始地址
start &= 0xfffff000;
从当前进程的局部描述符表中,取出数据段的基地址
start += get_base(current->ldt[2]);
while (size>0) {
size -= 4096;
进行页面写验证,如果不可写,那么为其分配一页面
write_verify(start);
以页面为单位进行验证
start += 4096;
}
}
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");
在内核0.11中支持的进程最大空间为64M
为新进程的代码段和数据段基地址付值
new_data_base = new_code_base = nr * 0x4000000;
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;
}
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;
为task数据结构分配一页
p = (struct task_struct *) get_free_page();
if (!p)
return -EAGAIN;
将当前新的任务结构设置到任务号为nr的任务槽中
task[nr] = p;
*p = *current;
暂时设置当前新创建的任务的状态为不可中断的。防止调度程序调度到他
p->state = TASK_UNINTERRUPTIBLE;
设置新进程的id
p->pid = last_pid;
将当前进程设置为新创建任务的父进程
p->father = current->pid;
设置时间片
p->counter = p->priority;
初始化信号位图,定时器
p->signal = 0;
p->alarm = 0;
初始化会话期领导进程标志
p->leader = 0;
初始化系统和用户时间
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;
}
由于子进程复制了父进程的文件描述符,所以需要将i节点的应用增加1
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;
return last_pid;
}
在任务槽中查找可用的任务槽
int find_empty_process(void)
{
int i;
为新进程找到一个可用的id
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++)
if (!task[i])
return i;
return -EAGAIN;
}