博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
2018-2019-1 20189215 《Linux内核原理与分析》第七周作业
阅读量:5226 次
发布时间:2019-06-14

本文共 6105 字,大约阅读时间需要 20 分钟。

《庖丁解牛》第六章书本知识总结

  1. 操作系统内个实现操作系统的三大管理功能:进程管理、内存管理、文件系统。分别对应《操作系统原理》中最重要的3个抽象概念是进程、虚拟内存和文件。
  2. Linux中的进程描述符struct task_struct就是PCB进程控制块。
  3. Linux内核管理的进程状态转换图
    1506992-20181125202420242-976088574.jpg
  4. 进程描述符struct task_struct记录了当前进程的父进程real_parentparent
  5. 双向链表struct list_head children记录当前进程的子进程。
  6. 双向链表struct list_head sibling记录当前进程的兄弟进程。
  7. fork系统调用创建了一个子进程,子进程复制了父进程中所有的进程信息,包括内核堆栈、进程描述符等,子进程作为一个独立的进程也会被调度。
  8. forkvforkclone系统调用和kernel_thread内核函数都可以创建一个新进程,而且都是通过do_fork函数来创建进程的,只不过传递的参数不同。
  9. fork一个子进程的过程中,复制父进程的资源时采用了Copy On Write(写时复制)技术,不需要修改进程资源,父子进程是共享内存存储空间的。
  10. do_fork主要完成了调用copy_process()复制父进程信息、获得pid、调用wake_up_new_task将子进程加入调度器队列等待获得分配CPU资源运行、通过clone_flags标志做一些辅助工作。
    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;    // ...    // 复制进程描述符,返回创建的task_struct的指针    p = copy_process(clone_flags, stack_start, stack_size,             child_tidptr, NULL, trace);    if (!IS_ERR(p)) {        struct completion vfork;        struct pid *pid;        trace_sched_process_fork(current, p);        // 取出task结构体内的pid        pid = get_task_pid(p, PIDTYPE_PID);        nr = pid_vnr(pid);        if (clone_flags & CLONE_PARENT_SETTID)            put_user(nr, parent_tidptr);        // 如果使用的是vfork,那么必须采用某种完成机制,确保父进程后运行        if (clone_flags & CLONE_VFORK) {            p->vfork_done = &vfork;            init_completion(&vfork);            get_task_struct(p);        }        // 将子进程添加到调度器的队列,使得子进程有机会获得CPU        wake_up_new_task(p);        // ...        // 如果设置了 CLONE_VFORK 则将父进程插入等待队列,并挂起父进程直到子进程释放自己的内存空间        // 保证子进程优先于父进程运行        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;}
  1. copy_process函数主要完成课调用dup_task_struct复制当前进程(父进程)描述符task_struct、信息检查、初始化、把进程状态设置为TASK_RUNNING(此时子进程置为就绪态)、采用写时复制技术逐一复制所有其他进程资源、调用copy_thread初始化子进程内核栈、设置子进程pid等。
    copy_process代码:
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;    ...    retval = security_task_create(clone_flags);//安全性检查    ...    p = dup_task_struct(current);   //复制PCB,为子进程创建内核栈、进程描述符    ftrace_graph_init_task(p);    ···        retval = -EAGAIN;    // 检查该用户的进程数是否超过限制    if (atomic_read(&p->real_cred->user->processes) >=            task_rlimit(p, RLIMIT_NPROC)) {        // 检查该用户是否具有相关权限,不一定是root        if (p->real_cred->user != INIT_USER &&            !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))            goto bad_fork_free;    }    ...    // 检查进程数量是否超过 max_threads,后者取决于内存的大小    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;    ...    spin_lock_init(&p->alloc_lock);          //初始化自旋锁    init_sigpending(&p->pending);           //初始化挂起信号     posix_cpu_timers_init(p);               //初始化CPU定时器    ···    retval = sched_fork(clone_flags, p);  //初始化新进程调度程序数据结构,把新进程的状态设置为TASK_RUNNING,并禁止内核抢占    ...    // 复制所有的进程信息    shm_init_task(p);    retval = copy_semundo(clone_flags, p);    ...    retval = copy_files(clone_flags, p);    ...    retval = copy_fs(clone_flags, p);    ...    retval = copy_sighand(clone_flags, p);    ...    retval = copy_signal(clone_flags, p);    ...    retval = copy_mm(clone_flags, p);    ...    retval = copy_namespaces(clone_flags, p);    ...    retval = copy_io(clone_flags, p);    ...    retval = copy_thread(clone_flags, stack_start, stack_size, p);// 初始化子进程内核栈    ...    //若传进来的pid指针和全局结构体变量init_struct_pid的地址不相同,就要为子进程分配新的pid    if (pid != &init_struct_pid) {        retval = -ENOMEM;        pid = alloc_pid(p->nsproxy->pid_ns_for_children);        if (!pid)            goto bad_fork_cleanup_io;    }    ...    p->pid = pid_nr(pid);    //根据pid结构体中获得进程pid    //若 clone_flags 包含 CLONE_THREAD标志,说明子进程和父进程在同一个线程组    if (clone_flags & CLONE_THREAD) {        p->exit_signal = -1;        p->group_leader = current->group_leader; //线程组的leader设为子进程的组leader        p->tgid = current->tgid;       //子进程继承父进程的tgid    } else {        if (clone_flags & CLONE_PARENT)            p->exit_signal = current->group_leader->exit_signal;        else            p->exit_signal = (clone_flags & CSIGNAL);        p->group_leader = p;          //子进程的组leader就是它自己                       p->tgid = p->pid;        //组号tgid是它自己的pid    }    ...        if (likely(p->pid)) {        ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);        init_task_pid(p, PIDTYPE_PID, pid);        if (thread_group_leader(p)) {            ...            // 将子进程加入它所在组的哈希链表中            attach_pid(p, PIDTYPE_PGID);            attach_pid(p, PIDTYPE_SID);            __this_cpu_inc(process_counts);        } else {            ...        }        attach_pid(p, PIDTYPE_PID);        nr_threads++;     //增加系统中的进程数目    }    ...    return p;             //返回被创建的子进程描述符指针P    ...}

clone, fork, vfork区别与联系

系统调用服务例程sys_clone, sys_fork, sys_vfork三者最终都是调用do_fork函数完成。

do_fork的参数与clone系统调用的参数类似,不过多了一个regs(内核栈保存的用户模式寄存器).,实际上其他的参数也都是用regs取的。

  • 具体实现的参数不同
  1. clone:
    clone的API外衣, 把fn, arg压入用户栈中, 然后引发系统调用. 返回用户模式后下一条指令就是fn.
    sysclone: parent_tidptr, child_tidptr都传到了 do_fork的参数中
    sysclone: 检查是否有新的栈, 如果没有就用父进程的栈 (开始地址就是regs.esp)
  2. fork, vfork:
    服务例程就是直接调用do_fork, 不过参数稍加修改
    clone_flags:
    sys_fork: SIGCHLD, 0, 0, NULL, NULL, 0
    sys_vfork: CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 0, NULL, NULL, 0
    用户栈: 都是父进程的栈.
    parent_tidptr, child_ctidptr都是NULL.

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

本次实验中使用的fork命令是用sys_clone系统调用实现的,因此断点设置在sys_clone

本次实验通过实践,调试应按照以下顺序进行。

  1. 配置好menuos,使用fork命令
    1506992-20181125210809612-736470682.png
  2. 先设置sys_clone断点
    1506992-20181125210842429-1439226120.png
  3. 运行到sys_clone后,设置其它断点`
    1506992-20181125210912813-2014665368.png
  4. 进入do_fork函数
    1506992-20181125210930555-1725407451.png
  5. 在do_fork函数中会调用copy_process
    1506992-20181125210948093-444261946.png
  6. 在copy_process中调用dup_task_struct
    1506992-20181125211006420-1311653961.png
  7. 在copy_process中调用copy_thread
    1506992-20181125211029500-825034640.png
  8. 子进程ret
    1506992-20181125211048230-978086982.png

实验过程流程图

1506992-20181125211117818-2058350061.png

参考资料

转载于:https://www.cnblogs.com/jsjliyang/p/10016951.html

你可能感兴趣的文章
发布一个JavaScript工具类库jutil,欢迎使用,欢迎补充,欢迎挑错!
查看>>
discuz 常用脚本格式化数据
查看>>
洛谷P2777
查看>>
PHPStorm2017设置字体与设置浏览器访问
查看>>
SQL查询总结 - wanglei
查看>>
安装cocoa pods时出现Operation not permitted - /usr/bin/xcodeproj的问题
查看>>
makefile中使用变量
查看>>
GIT笔记:将项目发布到码云
查看>>
JavaScript:学习笔记(7)——VAR、LET、CONST三种变量声明的区别
查看>>
JavaScript 鸭子模型
查看>>
SQL Server 如何查询表定义的列和索引信息
查看>>
GCD 之线程死锁
查看>>
NoSQL数据库常见分类
查看>>
一题多解 之 Bat
查看>>
Java 内部类
查看>>
{面试题7: 使用两个队列实现一个栈}
查看>>
【练习】使用事务和锁定语句
查看>>
centos7升级firefox的flash插件
查看>>
Apache Common-IO 使用
查看>>
再谈Vmware NAT的配置和路由流程
查看>>