《Linux内核分析》 week6作业-Linux内核fork()系统调用的创建过程

一.进程控制块PCB-stack_struct

进程在操作系统中都有一个结构,用于表示这个进程。这就是进程控制块(PCB),在Linux中具体实现是task_struct数据结构,它主要记录了以下信息:

  • 状态信息,例如可执行状态、就绪状态、阻塞状态等。
  • 性质,由于unix有很多变种,进行有自己独特的性质。
  • 资源,资源的链接比如内存,还有资源的限制和权限等。
  • 组织,例如按照家族关系建立起来的树(父进程、子进程等)。

task_struct结构体内容非常庞大,暂时没有去分析源代码,以后有时间再去研究。

二.Linux fork执行的过程

在menu中添加一个fork的系统调用,然后用gdb开始调试.执行以下命令

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -s
gdb
file linux-3.18.6/vmlinux
target remote:1234

然后在sys_fork、sys_clone处设置断点,再逐步调试,观察fork系统调用的执行过程。

具体分析fork系统调用执行过程.

1.fork、vfork和clone三个系统调用都可以创建一个新进程,而且它们都是通过调用do_fork来实现进程的创建,do_fork通过传递不同的clone_flags来实现fork、clone、vfork。

long do_fork(unsigned long clone_flags,
1624          unsigned long stack_start,
1625          unsigned long stack_size,
1626          int __user *parent_tidptr,
1627          int __user *child_tidptr)
1628{
1629    struct task_struct *p;
1630    int trace = 0;
1631    long nr;
1632
1633    /*
1634     * Determine whether and which event to report to ptracer.  When
1635     * called from kernel_thread or CLONE_UNTRACED is explicitly
1636     * requested, no event is reported; otherwise, report if the event
1637     * for the type of forking is enabled.
1638     */
1639    if (!(clone_flags & CLONE_UNTRACED)) {
1640        if (clone_flags & CLONE_VFORK)
1641            trace = PTRACE_EVENT_VFORK;
1642        else if ((clone_flags & CSIGNAL) != SIGCHLD)
1643            trace = PTRACE_EVENT_CLONE;
1644        else
1645            trace = PTRACE_EVENT_FORK;
1646
1647        if (likely(!ptrace_event_enabled(current, trace)))
1648            trace = 0;
1649    }
1650
1651    p = copy_process(clone_flags, stack_start, stack_size,
1652             child_tidptr, NULL, trace);    #进程复制,核心函数
1653    /*
1654     * Do this prior waking up the new thread - the thread pointer
1655     * might get invalid after that point, if the thread exits quickly.
1656     */
1657    if (!IS_ERR(p)) {
1658        struct completion vfork;
1659        struct pid *pid;
1660
1661        trace_sched_process_fork(current, p);
1662
1663        pid = get_task_pid(p, PIDTYPE_PID);
1664        nr = pid_vnr(pid);
1665
1666        if (clone_flags & CLONE_PARENT_SETTID)
1667            put_user(nr, parent_tidptr);
1668
1669        if (clone_flags & CLONE_VFORK) {
1670            p->vfork_done = &vfork;
1671            init_completion(&vfork);
1672            get_task_struct(p);
1673        }
1674
1675        wake_up_new_task(p);
1676
1677        /* forking complete and child started to run, tell ptracer */
1678        if (unlikely(trace))
1679            ptrace_event_pid(trace, pid);
1680
1681        if (clone_flags & CLONE_VFORK) {
1682            if (!wait_for_vfork_done(p, &vfork))
1683                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
1684        }
1685
1686        put_pid(pid);
1687    } else {
1688        nr = PTR_ERR(p);
1689    }
1690    return nr;
1691}
1692

do_fork()函数的核心是copy_process(),该函数完成了进程创建的绝大部分。

/*
1175 * This creates a new process as a copy of the old one,
1176 * but does not actually start it yet.
1177 *
1178 * It copies the registers, and all the appropriate
1179 * parts of the process environment (as per the clone
1180 * flags). The actual kick-off is left to the caller.
1181 */
1182static struct task_struct *copy_process(unsigned long clone_flags,
1183                    unsigned long stack_start,
1184                    unsigned long stack_size,
1185                    int __user *child_tidptr,
1186                    struct pid *pid,
1187                    int trace)
1188{
1189    int retval;
1190    struct task_struct *p;
1191
1192    if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
1193        return ERR_PTR(-EINVAL);
1194
1195    if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
1196        return ERR_PTR(-EINVAL);
1197
1198    /*
1199     * Thread groups must share signals as well, and detached threads
1200     * can only be started up within the thread group.
1201     */
1202    if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
1203        return ERR_PTR(-EINVAL);
1204
1205    /*
1206     * Shared signal handlers imply shared VM. By way of the above,
1207     * thread groups also imply shared VM. Blocking this case allows
1208     * for various simplifications in other code.
1209     */
1210    if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
1211        return ERR_PTR(-EINVAL);
1212
1213    /*
1214     * Siblings of global init remain as zombies on exit since they are
1215     * not reaped by their parent (swapper). To solve this and to avoid
1216     * multi-rooted process trees, prevent global and container-inits
1217     * from creating siblings.
1218     */
1219    if ((clone_flags & CLONE_PARENT) &&
1220                current->signal->flags & SIGNAL_UNKILLABLE)
1221        return ERR_PTR(-EINVAL);
1222
1223    /*
1224     * If the new process will be in a different pid or user namespace
1225     * do not allow it to share a thread group or signal handlers or
1226     * parent with the forking task.
1227     */
1228    if (clone_flags & CLONE_SIGHAND) {
1229        if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
1230            (task_active_pid_ns(current) !=
1231                current->nsproxy->pid_ns_for_children))
1232            return ERR_PTR(-EINVAL);
1233    }
1234
1235    retval = security_task_create(clone_flags);
1236    if (retval)
1237        goto fork_out;
1238
1239    retval = -ENOMEM;
1240    p = dup_task_struct(current);  #为子进程创建一个新的内核栈,复制task_struct和thread_info结构,此时子进程的进程控制块和父进程完全一致。
1241    if (!p)
1242        goto fork_out;
1243
1244    ftrace_graph_init_task(p);
1245
1246    rt_mutex_init_task(p);
1247
1248#ifdef CONFIG_PROVE_LOCKING
1249    DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
1250    DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
1251#endif
1252    retval = -EAGAIN;
1253    if (atomic_read(&p->real_cred->user->processes) >=
1254            task_rlimit(p, RLIMIT_NPROC)) {
1255        if (p->real_cred->user != INIT_USER &&
1256            !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
1257            goto bad_fork_free;
1258    }
1259    current->flags &= ~PF_NPROC_EXCEEDED;
1260
1261    retval = copy_creds(p, clone_flags);
1262    if (retval < 0)
1263        goto bad_fork_free;
1264
1265    /*
1266     * If multiple threads are within copy_process(), then this check
1267     * triggers too late. This doesn‘t hurt, the check is only there
1268     * to stop root fork bombs.
1269     */
1270    retval = -EAGAIN;
1271    if (nr_threads >= max_threads)
1272        goto bad_fork_cleanup_count;
1273
1274    if (!try_module_get(task_thread_info(p)->exec_domain->module))
1275        goto bad_fork_cleanup_count;
1276
1277    delayacct_tsk_init(p);    /* Must remain after dup_task_struct() */
1278    p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
1279    p->flags |= PF_FORKNOEXEC;
1280    INIT_LIST_HEAD(&p->children);
1281    INIT_LIST_HEAD(&p->sibling);
1282    rcu_copy_process(p);
1283    p->vfork_done = NULL;
1284    spin_lock_init(&p->alloc_lock);
1285
1286    init_sigpending(&p->pending);
1287
1288    p->utime = p->stime = p->gtime = 0;
....

通过dup_task_struct()函数,为子进程创建一个新的内核栈,复制task_struct和thread_info结构。

ti=alloc_thread_info_node(task,node);
tsk->stack=ti;
setup_thread_stack(tsk,orig); //这里只是复制了thread_info

重点关注下,fork()创建子进程后,父进程从系统调用中返回,而子进程从哪开始返回.

这主要是在copy_process()中copy_thread()代码.

int copy_thread(unsigned long clone_flags, unsigned long sp,
133    unsigned long arg, struct task_struct *p)
134{
135    struct pt_regs *childregs = task_pt_regs(p);
136    struct task_struct *tsk;
137    int err;
138
139    p->thread.sp = (unsigned long) childregs; #记录进程切换时的堆栈指针
140    p->thread.sp0 = (unsigned long) (childregs+1);
141    memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
142
143    if (unlikely(p->flags & PF_KTHREAD)) {
144        /* kernel thread */
145        memset(childregs, 0, sizeof(struct pt_regs));
146        p->thread.ip = (unsigned long) ret_from_kernel_thread;
147        task_user_gs(p) = __KERNEL_STACK_CANARY;
148        childregs->ds = __USER_DS;
149        childregs->es = __USER_DS;
150        childregs->fs = __KERNEL_PERCPU;
151        childregs->bx = sp;    /* function */
152        childregs->bp = arg;
153        childregs->orig_ax = -1;
154        childregs->cs = __KERNEL_CS | get_kernel_rpl();
155        childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
156        p->thread.io_bitmap_ptr = NULL;
157        return 0;
158    }
159    *childregs = *current_pt_regs();#复制内核堆栈
160    childregs->ax = 0; #这也是为什么子进程的fork返回0
161    if (sp)
162        childregs->sp = sp;
163
164    p->thread.ip = (unsigned long) ret_from_fork; #子进程开始执行处
165    task_user_gs(p) = get_user_gs(current_pt_regs());
166
167    p->thread.io_bitmap_ptr = NULL;
168    tsk = current;
169    err = -ENOMEM;
170
171    if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
172        p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
173                        IO_BITMAP_BYTES, GFP_KERNEL);
174        if (!p->thread.io_bitmap_ptr) {
175            p->thread.io_bitmap_max = 0;
176            return -ENOMEM;
177        }
178        set_tsk_thread_flag(p, TIF_IO_BITMAP);
179    }
180
181    err = 0;
182
183    /*
184     * Set a new TLS for the child thread?
185     */
186    if (clone_flags & CLONE_SETTLS)
187        err = do_set_thread_area(p, -1,
188            (struct user_desc __user *)childregs->si, 0);
189
190    if (err && p->thread.io_bitmap_ptr) {
191        kfree(p->thread.io_bitmap_ptr);
192        p->thread.io_bitmap_max = 0;
193    }
194    return err;
   } 

然后回到do_fork()函数中,唤醒子进程并开始运行。至此,一个进程创建就完成了。

三.实验总结

中间虽然的很多细节还不是很清楚,但是对linux 创建子进程的大体流程有了一个宏观的认识,更加深刻地理解了底层Linux 内核进程运行的机制。

时间: 2024-10-10 10:55:52

《Linux内核分析》 week6作业-Linux内核fork()系统调用的创建过程的相关文章

windows7内核分析之x86&amp;x64第二章系统调用

windows7内核分析之x86&x64第二章系统调用 2.1内核与系统调用 上节讲到进入内核五种方式 其中一种就是 系统调用 syscall/sysenter或者int 2e(在 64 位环境里统一使用 syscall/sysret 指令,在 32 位环境里统一使用 sysenter/sysexit 在 compatibility 模式下必须切换到 64 位模式,然后使用 syscall/sysret 指令 注释:32位cpu是x86模式 也叫legacy模式 再说清楚点 就是包含了实模式:可

《Linux内核分析》 第五节 扒开系统调用的三层皮(下)

摘要:范闻泽 原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 1.实验环境是使用本课程配置的实验楼虚拟机环境,打开命令行客户端,cd LinuxKernel目录,使用命令rm -rf menu 删除原来的代码,使用git clone https://github.com/mengning/menu.git获取menu的最新代码,然后cd menu进入menu子文件夹,使用vi test.c

Linux内核分析之六——进程的描述与进程的创建

作者:姚开健 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 进程的描述 Linux系统的进程由一个进程描述符PCB,即task_struct结构体来描述,其在内核中代码实现如下: struct task_struct { 1236 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ 1237 void

Linux内核分析——进程的切换和系统的一般执行过程

进程的切换和系统的一般执行过程 一.进程切换的关键代码switch_to分析 (一)进程调度与进程调度的时机分析 1.不同类型的进程有不同的调度需求 第一种分类: (1)I/O-bound:频繁进行I/O,花费很多时间等待I/O操作的完成. (2)CPU-bound:计算密集型,需要大量CPU时间进行计算. 第二种分类: (1)批处理进程:不必交互.很快响应. (2)实时进程:要求响应时间短. (3)交互式进程(shell). 2.调度策略:是一组规则,它们决定什么时候以怎样的方式选择一个新进程

20135239 益西拉姆 linux内核分析 进程的切换和系统的一般执行过程

week 8 进程的切换和系统的一般执行过程 [ 20135239 原文请转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] 一.进程调度与进程调度的时机分析 操作系统原理中介绍了大量进程调度算法,这些算法从实现的角度看仅仅是从运行队列中选择一个新进程,选择的过程中运用了不同的策略而已.对于理解操作系统的工作机制,反而是进程的调度时机与进程的切换机制更为关键. 不同类型的进程有不同的调度需求 第一

Linux内核分析——第一章 Linux内核简介

第一章   Linux内核简介 一.Unix的历史 1.Unix系统成为一个强大.健壮和稳定的操作系统的根本原因: (1)简洁 (2)在Unix中,很多东西都被当做文件对待.这种抽象使对数据和对设备的操作都是通过一套相同的系统调用借口来进行的:open(),read(),write(),lseek()和close(). (3)Unix的内核和相关的系统工具软件是用C语言编写而成.(移植能力) (4)进程创建非常迅速,并且有一个非常独特的fork()系统调用. (5)Unix提供了一套非常简单但又

20135201李辰希 《Linux内核分析》第五周 扒开系统调用的“三层皮”(下)

李辰希  原创作品转载请注明出处 <Linux内核分析> MOOC课程http://mooc.study.163.com/course/USTC-100002900 一.给MenusOS增加time和time-asm命令 1.操作步骤 进入实验楼 首先,强制删除当前的menu 克隆一个新的menu 进入menu之后,输入make rootfs,就可以自动编译 输入help,可以发现系统支持更多的命令: help version quit time time-asm 那么,time和time-a

《Linux内核分析》第四周笔记 扒开系统调用的三层皮(上)

扒开系统调用的三层皮(上) 一.用户态.内核态和中断 库函数将系统调用封装起来. 1.什么是用户态和内核态 一般现代CPU都有几种不同的指令执行级别. 在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态. 而在相应的低级别执行状态下(用户态),代码的掌控范围会受到限制.只能在对应级别允许的范围内活动.系统容易崩溃. 在intel X86CPU有四种不同的执行级别0,1,2,3,linux只使用了0级和3级分别来表示内核态和用户态. 2.在linux内核代码

fork 系统调用的执行过程与调试

我们可以通过fork系统调用来处理进程创建的任务.对于进程的创建, 可以sys_clone, sys_vfork,以及sys_fork. 这些系统调用的内部都使用了do_fork.函数. 对于do_fork函数, 会copy tast_struct, 设置内核堆栈, 并且对一些特定的数据结构进行修改.其中里面还有copy_thread 函数, 会设置这个进程的cs和ip.这个是在进程的thread_info中保持的.这里的ip设置成了ret_from_fork函数(在ret_from_frok里