Linux进程启动过程简析

朱宇轲 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

今天,我们将通过阅读linux的内核代码来对linux系统中进程的创建过程进行简单的分析。

大家都知道,linux通过进程控制块PCB来对进程进行控制和管理,它存放了进程的数据。在linux中,PCB的代码如下(当然是节选的==):

struct task_struct {
    volatile long state;//进程状态
    void *stack;//进程堆栈指针
    atomic_t usage;
    unsigned int flags;
    unsigned int ptrace;
...
    //一些记录优先级的变量
    int prio, static_prio, normal_prio;
    unsigned int rt_priority;
    const struct sched_class *sched_class;
    struct sched_entity se;
    struct sched_rt_entity rt;
...
    //进程链表
    struct list_head tasks;
#ifdef CONFIG_SMP
    struct plist_node pushable_tasks;
    struct rb_node pushable_dl_tasks;
#endif
    //pid号
    pid_t pid;
    pid_t tgid;
...
    //父进程以及子进程
    struct task_struct __rcu *real_parent;
    struct task_struct __rcu *parent; 

    struct list_head children;
    struct list_head sibling;
    struct task_struct *group_leader;
...
};

  可以看到,PCB中记录了进程的ID号、优先级、状态、与其他进程关系等信息,操作系统由此对进程进行管理。

  需要注意的是,在操作系统内核的具体实现中,是将当前的进程全部存入到一个循环链表中来进行管理的。如下图所示:

具体的实验则是利用gdb调试进程创建的函数,实验截图如下:

此次我们在6个函数(or 系统调用)处设置了断点:

1.sys_clone
2.do_fork
3.dup_task_struct
4.copy_process
5.copy_thread
6.ret_from_fork

在实验楼所使用的虚拟系统中,进程创建的底层函数是sys_clone,而sys_clone实际上调用的是do_fork,因此可以说do_fork才是真正实现了创建进程细节的函数。

long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    struct task_struct *p;
    int trace = 0;
    long nr;

    ......

    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace);
    ......
}

 在这个函数里,p是记录了新的进程的PCB的变量,通过copy_process,系统将父进程的PCB拷贝并修改到子进程中。

copy_process函数则是首先利用dup_task_struct将父进程的PCB全盘拷贝,之后再具体修改与父进程不同的子部分。

static struct task_struct *copy_process(unsigned long clone_flags,
                    unsigned long stack_start,
                    unsigned long stack_size,
                    int __user *child_tidptr,
                     struct pid *pid,
                    int trace)
{
    int retval;
    struct task_struct *p;

    //拷贝父进程的PCB
    p = dup_task_struct(current);
    if (!p)
        goto fork_out;

    //修改具体的部分结构
    p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
    p->flags |= PF_FORKNOEXEC;
    INIT_LIST_HEAD(&p->children);
    INIT_LIST_HEAD(&p->sibling);
    rcu_copy_process(p);
    p->vfork_done = NULL;
    spin_lock_init(&p->alloc_lock);
...
retval=copy_thread(clone_flags,stack_start,stack_size,p);
}

  需要注意的是,在copy_process函数中有一个copy_thread函数,在它的函数实现中,将创建的子进程的栈底空间找到,并根据此修改了子进程的ip和sp数据:sp指向栈底,而ip指向ret_from_fork段。那ret_from_fork段又是什么呢,它其实就是下面一段代码:

ENTRY(ret_from_fork)
    CFI_STARTPROC
    pushl_cfi %eax
    call schedule_tail
    GET_THREAD_INFO(%ebp)
    popl_cfi %eax
    pushl_cfi $0x0202        # Reset kernel eflags
    popfl_cfi
    jmp syscall_exit
    CFI_ENDPROC
END(ret_from_fork)

  可以看到,它最终跳转到了syscall_exit,而syscall_exit就是我们上次分析的中断处理程序sys_call中的代码。这样,sp指向的实际是我们触发终端后存储的上下文的那块儿堆栈,ip则指向syscall_eixt,当新的进程创建时,它会首先执行syscall_exit处的代码,不久就会遇到Restore_All,恢复上下文环境,新的进程得以执行。

总结:

  在linux内核中,创建新的进程时,基本思路是复制父进程的PCB给子进程,如果有不同的数据再进行调整。同时,将子进程执行的第一条命令指向中断恢复的代码,从而实现创建新进程的效果。

时间: 2024-11-05 03:55:30

Linux进程启动过程简析的相关文章

linux文件系统写过程简析

linux写入磁盘过程经历VFS ->  页缓存(page cache) -> 具体的文件系统(ext2/3/4.XFS.ReiserFS等) -> Block IO ->设备驱动 -> SCSI指令(或者其他指令),总体来说linux文件写入磁盘过程比较复杂 1.VFS(虚拟文件系统) Linux中采用了VFS的方式屏蔽了多个文件系统的差别, 当需要不同的设备或者其他文件系统时,采用挂载mount的方式访问其他设备或者其他文件系统(这里可以把文件系统理解为具体的设备).正是

netty4 bind启动过程简析

请看一下简单的 一个netty4服务端启动代码样例 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)

【转载】简述Linux的启动过程

原文:简述Linux的启动过程 本文将简单介绍一下Linux的启动过程,希望对那些安装Linux的过程中遇到了问题的朋友有些帮助 声明:本人没用过UEFI模式和GPT分区格式,所有关于这两部分的内容都是网络上找的资料,仅供参考. 典型启动顺序 计算机通电后,CPU开始从一个固定的地址加载代码并开始执行,这个地址就是BIOS的驱动程序所在的位置,于是BIOS的驱动开始执行. BIOS驱动首先进行一些自检工作,然后根据配置的启动顺序,依次尝试加载启动程序.比如配置的启动顺序是CD->网卡01->U

Linux系统--Linux的启动过程

Linux系统--Linux启动过程 CentOS 启动流程: POST --> Boot Sequence(BIOS) --> Boot Loader (MBR) --> Kernel(ramdisk) --> rootfs --> switchroot --> /sbin/init -->(/etc/inittab, /etc/init/*.conf) --> 设定默认运行级别 --> 系统初始化脚本 --> 关闭或启动对应级别下的服务 --

linux服务器启动过程

随着Linux的应用日益广泛,特别是在网络应用方面,有大量的网络服务器使用Linux操作系统.由于Linux的桌面应用和Windows相比还有一 定的差距,所以在企业应用中往往是Linux和Windows操作系统共存形成异构网络.在服务器端大多使用Linux和Unix的,目前Linux的擅 长应用领域是单一应用的基础服务器应用,譬如DNS和DHCP服务器.Web服务器.目录服务器.防火墙.文件和打印服务器.Intranet代理服务器 .启动 Linux 系统的过程包括很多阶段.不管您是引导一个标

回眸总结linux的启动过程

学弟问我linux的启动过程,突然被雷到了,竟然忘的那么透彻,脑袋飘来6个字--"岁月是把杀猪刀",于是恶补 ,写成日志,起码原理上的东西不能丢,发展才是硬道理,最近做一个高并发(70万)的服务测试中也深深感受到对linux系统的理解还需要更加的深刻,废话不多说,总结如下:       1.系统加电后,bios读取硬件信息,读取启动设备,读取0磁头0柱面的1扇区的主引导记录mbr,并将启动控制权移交给mbr; 2.mbr有512字节三部分组成,其中前446字节是bootloader主引

Nutch学习笔记——抓取过程简析

Nutch学习笔记二--抓取过程简析 学习环境: ubuntu 概要: Nutch 是一个开源Java 实现的搜索引擎.它提供了我们运行自己的搜索引擎所需的全部工具.包括全文搜索和Web爬虫. 通过nutch,诞生了hadoop.tika.gora. 先安装SVN和Ant环境.(通过编译源码方式来使用nutch) apt-get install ant apt-get install subversion [email protected]:~/data/nutch$ svn co https:

linux内核启动过程学习总结

下面是学习linux内核启动过程的记录 平台是:powerpc mpc8548 + linux2.6.23 内核 通用寄存器的作用r0 :在函数开始时使用r1 :存放堆栈指针,相当于ia32架构中的esp寄存器r2 :存放当前进程的描述符的地址r3 :存放第一个参数和返回地址r4-r10 :存放函数的参数r11 :用在指针的调用和当前一些语言的环境指针r12 :用于存放异常处理r13 :保留做为系统线程IDr14-r31 :作为本地变量,具有非易失性 Linux启动过程描述 第一步:使用Boot

Android --- Zygote和System进程启动过程简要分析

Android --- Zygote和System进程启动过程简要分析 在看过<Android情景源代码>的Zygote启动章节后,作如下简要总结.Zygote进程在init进程启动过程中被以service服务的形式启动: service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main socket zygote stream 660 root syste