2022-2023-1 20222812 《Linux内核原理与分析》第七周作业

实验六:分析 Linux 内核创建一个新进程的过程

一、相关知识

a.操作系统内核三大功能是进程管理,内存管理,文件系统,最核心的是进程管理。

b.Linux 进程的状态和操作系统原理的描述进程状态有所不同,比如就绪状态和运行状态都是TASK_RUNNING。(这个表示它是可运行的,但是实际上有没有在运行取决于它是否占有 CPU)。

c.fork 被调用一次,能够返回两次。在父进程中返回新创建子进程的 pid;在子进程中返回 0。

d.调用 fork 之后,数据、堆、栈有两份,代码仍然为一份(这个代码段成为两个进程的共享代码段)。当父子进程有一个想要修改数据或者堆栈时,两个进程真正分裂。

二、内核代码分析

SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
	return do_fork(SIGCHLD, 0, 0, NULL, NULL);
#else
	return -EINVAL;
#endif
}
SYSCALL_DEFINE0(vfork)
{
	return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
			0, NULL, NULL);
}

 

 通过上面的代码可以看出 fork、vfork 和 clone 三个系统调用都可以创建一个新进程,而且都是通过 do_fork 来创建进程,只不过传递的参数不同。

(1)do_fork

do_fork()主要完成了调用 copy_process() 复制父进程信息、获得pid、调用 wake_up_new_task 将子进程加入调度器队列,为之分配 CPU、通过 clone_flags 标志做一些辅助工作。其中 copy_process()是创建一个进程内容的主要的代码。

 (2)copy_process

 copy_process 主要完成了调用 dup_task_struct 复制当前的 task_struct、信息检查、初始化、把进程状态设置为 TASK_RUNNING、复制所有进程信息、调用 copy_thread 初始化子进程内核栈、设置子进程pid。

(3)dup_task_struct

static struct task_struct *dup_task_struct(struct task_struct *orig)
{
    struct task_struct *tsk;
    struct thread_info *ti;
    int node = tsk_fork_get_node(orig);
    int err;
    tsk = alloc_task_struct_node(node);    //为子进程创建进程描述符
    ...
    ti = alloc_thread_info_node(tsk, node); //实际上是创建了两个页,一部分用来存放 thread_info,一部分就是内核堆栈
    ...
    err = arch_dup_task_struct(tsk, orig);  //复制父进程的task_struct信息
    ...
    tsk->stack = ti;                  // 将栈底的值赋给新结点的stack
   
    setup_thread_stack(tsk, orig);//对子进程的thread_info结构进行初始化(复制父进程的thread_info 结构,然后将 task 指针指向子进程的进程描述符)
    ...
    return tsk;               // 返回新创建的进程描述符指针
    ...
}

(4)copy_thread

int copy_thread(unsigned long clone_flags, unsigned long sp,
    unsigned long arg, struct task_struct *p)
{
 
    
    struct pt_regs *childregs = task_pt_regs(p);
    struct task_struct *tsk;
    int err;
 
    p->thread.sp = (unsigned long) childregs;
    p->thread.sp0 = (unsigned long) (childregs+1);
    memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
 
    
    if (unlikely(p->flags & PF_KTHREAD)) {
        /* kernel thread */
        memset(childregs, 0, sizeof(struct pt_regs));
      
        p->thread.ip = (unsigned long) ret_from_kernel_thread; //如果创建的是内核线程,则从ret_from_kernel_thread开始执行
        task_user_gs(p) = __KERNEL_STACK_CANARY;
        childregs->ds = __USER_DS;
        childregs->es = __USER_DS;
        childregs->fs = __KERNEL_PERCPU;
        childregs->bx = sp; /* function */
        childregs->bp = arg;
        childregs->orig_ax = -1;
        childregs->cs = __KERNEL_CS | get_kernel_rpl();
        childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
        p->thread.io_bitmap_ptr = NULL;
        return 0;
    }
 
    
    *childregs = *current_pt_regs();//复制内核堆栈(复制父进程的寄存器信息,即系统调用SAVE_ALL压栈的那一部分内容)
    
    childregs->ax = 0;           //子进程的eax置为0,所以fork的子进程返回值为0
    ...
    p->thread.ip = (unsigned long) ret_from_fork;//ip指向 ret_from_fork,子进程从此处开始执行
    task_user_gs(p) = get_user_gs(current_pt_regs());
    ...
    return err;

dup_task_struct 只是为子进程创建一个内核栈,copy_thread 才真正完成赋值。

三、实验

1、用户态创建进程的实验

 2.通过实验跟踪分析进程创建的过程

在MenuOS中增加命令fork:

 MenuOS的运行的结果:

 设置几个断点:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值