linux内核分析之fork.c

本文深入解析了Linux系统中进程复制的具体实现过程,包括关键函数的使用、内存区域验证及复制等核心步骤。通过代码示例展示了如何在内核级别创建新进程,并详细解释了进程上下文、内存管理和文件描述符等重要概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值