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

时间:2022-07-05 09:10:28

一.进程控制块PCB-stack_struct

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

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

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

二.Linux fork执行的过程

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

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

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

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

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

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

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 = ;
long nr; /*
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 */
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace)))
trace = ;
}
1650
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace); #进程复制,核心函数
/*
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 */
if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid; trace_sched_process_fork(current, p); pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid); if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr); if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
} wake_up_new_task(p); /* forking complete and child started to run, tell ptracer */
if (unlikely(trace))
ptrace_event_pid(trace, pid); if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
} put_pid(pid);
} else {
nr = PTR_ERR(p);
}
return nr;
}

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 */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; if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL); if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
return ERR_PTR(-EINVAL); /*
1199 * Thread groups must share signals as well, and detached threads
1200 * can only be started up within the thread group.
1201 */
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL); /*
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 */
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL); /*
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 */
if ((clone_flags & CLONE_PARENT) &&
current->signal->flags & SIGNAL_UNKILLABLE)
return ERR_PTR(-EINVAL); /*
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 */
if (clone_flags & CLONE_SIGHAND) {
if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
(task_active_pid_ns(current) !=
current->nsproxy->pid_ns_for_children))
return ERR_PTR(-EINVAL);
} retval = security_task_create(clone_flags);
if (retval)
goto fork_out; retval = -ENOMEM;
p = dup_task_struct(current); #为子进程创建一个新的内核栈,复制task_struct和thread_info结构,此时子进程的进程控制块和父进程完全一致。
if (!p)
goto fork_out; ftrace_graph_init_task(p); rt_mutex_init_task(p); #ifdef CONFIG_PROVE_LOCKING
DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
#endif
retval = -EAGAIN;
if (atomic_read(&p->real_cred->user->processes) >=
task_rlimit(p, RLIMIT_NPROC)) {
if (p->real_cred->user != INIT_USER &&
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
goto bad_fork_free;
}
current->flags &= ~PF_NPROC_EXCEEDED; retval = copy_creds(p, clone_flags);
if (retval < )
goto bad_fork_free; /*
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 */
retval = -EAGAIN;
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count; if (!try_module_get(task_thread_info(p)->exec_domain->module))
goto bad_fork_cleanup_count; delayacct_tsk_init(p); /* Must remain after dup_task_struct() */
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); init_sigpending(&p->pending); p->utime = p->stime = p->gtime = ;
....

通过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,
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+);
memset(p->thread.ptrace_bps, , sizeof(p->thread.ptrace_bps)); if (unlikely(p->flags & PF_KTHREAD)) {
/* kernel thread */
memset(childregs, , sizeof(struct pt_regs));
p->thread.ip = (unsigned long) 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 = -;
childregs->cs = __KERNEL_CS | get_kernel_rpl();
childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
p->thread.io_bitmap_ptr = NULL;
return ;
}
*childregs = *current_pt_regs();#复制内核堆栈
childregs->ax = ; #这也是为什么子进程的fork返回0
if (sp)
childregs->sp = sp; p->thread.ip = (unsigned long) ret_from_fork; #子进程开始执行处
task_user_gs(p) = get_user_gs(current_pt_regs()); p->thread.io_bitmap_ptr = NULL;
tsk = current;
err = -ENOMEM; if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
IO_BITMAP_BYTES, GFP_KERNEL);
if (!p->thread.io_bitmap_ptr) {
p->thread.io_bitmap_max = ;
return -ENOMEM;
}
set_tsk_thread_flag(p, TIF_IO_BITMAP);
} err = ; /*
184 * Set a new TLS for the child thread?
185 */
if (clone_flags & CLONE_SETTLS)
err = do_set_thread_area(p, -,
(struct user_desc __user *)childregs->si, ); if (err && p->thread.io_bitmap_ptr) {
kfree(p->thread.io_bitmap_ptr);
p->thread.io_bitmap_max = ;
}
return err;

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

三.实验总结

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

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

  1. 《Linux内核分析》 第四节 扒开系统调用的三层皮(上)

    <Linux内核分析> 第四节 扒开系统调用的三层皮(上) 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com ...

  2. LINUX内核分析第四周学习总结——扒开系统调用的&OpenCurlyDoubleQuote;三层皮”

    LINUX内核分析第四周学习总结--扒开系统调用的"三层皮" 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>MOOC ...

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

    <Linux内核分析> 第五节 扒开系统调用的三层皮(下) 20135307 一.给MenusOS增加time和time-asm命令 给MenuOS增加time和time-asm命令需要 ...

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

    windows7内核分析之x86&x64第二章系统调用 2.1内核与系统调用 上节讲到进入内核五种方式 其中一种就是 系统调用 syscall/sysenter或者int 2e(在 64 位环 ...

  5. 《linux内核分析》作业一:分析汇编代码

    通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的(王海宁) 姓名:王海宁                             学号:20135103 课程:<Linux内核分析& ...

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

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FIVE( ...

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

    进程的切换和系统的一般执行过程 一.进程切换的关键代码switch_to分析 (一)进程调度与进程调度的时机分析 1.不同类型的进程有不同的调度需求 第一种分类: (1)I/O-bound:频繁进行I ...

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

    week 8 进程的切换和系统的一般执行过程 [ 20135239 原文请转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

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

    进程调度的时机 linux进程调度是基于分时和优先级的 中断处理过程(包括时钟中断.I/O中断.系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用s ...

随机推荐

  1. MongoDB基础入门002--基本操作,增删改查

    一.这里只是演示最基本的操作,更多的信息可以去官网.https://docs.mongodb.com/manual 打开一个cmd,输入mongo命令打开shell,其实这个shell就是mongod ...

  2. 第一次作业——subway

    作业源程序代码:https://github.com/R-81/subway 作业程序使用说明:通过输入命令参数求解路线(仅支持-b,-c),根据参数得出路线后,程序不会结束,此时可输入地铁路线名(例 ...

  3. ACM&lowbar;1 大数求和

    /*1 *2014.11.18 *大数求和 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <strin ...

  4. 项目使用中Linq使用总结

    项目使用中Linq使用总结 本文旨在和网友分享Linq在项目中的实践,曾经我参与过的项目都能看见Linq的影子.(LinqTosql.LinqToString.LinqToXML.LinqToEnti ...

  5. net start mysql服务名无效

    解决方案:(参考以下命令) 1.win+R键输入cmd敲回车进入dos界面: 2.输入cd d:/mysql-5.5.25/bin敲回车,发现没变化: 3.输入d:敲回车,定位到d:\mysql-5. ...

  6. 解决nginx负载均衡的session共享问题

    1.不使用session,换用cookie session是存放在服务器端的,cookie是存放在客户端的,我们可以把用户访问页面产生的session放到cookie里面,就是以cookie为中转站. ...

  7. JVM笔记4-对象的创建

    1.对象的创建过程: 1.new 类名 2.根据new的参数在常量池中定位一个类的符号的引用. 3.如果没找到这个符号的引用,说明类还没有被加载.则进行类的加载,解析和初始化 4.虚拟机为对象分配内存 ...

  8. xlsx导入成--json

    这两天遇到大难题了,就是这个   xlsx   导入问题,之前用的xlsx.full.min.js,写的导入,结果不兼容ie浏览器,研究这个也好长时间,网上居然还没有搜到合适的,自己写从xlsx官网上 ...

  9. 树状DP HDU1520 Anniversary party

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1520 题意:职员之间有上下级关系,每个职员有自己的happy值,越高在派对上就越能炒热气氛.但是必须是 ...

  10. IC卡插入与触点激活时序

    当IC卡插入接口设备时,终端应确保其所有触点处于低电平状态: 当IC卡插入接口设备后,触点须按如下方式激活: 要点: 终端必须在整个激活时序中保持RST为低电平状态: 触点物理接触之后,应在IO或CL ...