zoukankan      html  css  js  c++  java
  • 2017-2018-1 20179205《Linux内核原理与设计》第七周作业

    《Linux内核原理与设计》第七周作业

    视频学习及操作分析

    创建一个新进程在内核中的执行过程 fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;函数返回了两次,即在父进程子进程中各返回一次。通过复制当前进程可以创建一个新的进程。Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:

    ·复制一个PCB——task_struct

    1.err = arch_dup_task_struct(tsk, orig);``
    

    ·要给新进程分配一个新的内核堆栈

    1.ti = alloc_thread_info_node(tsk, node);
    2.tsk->stack = ti;
    3.setup_thread_stack(tsk, orig);   //这里只是复制thread_info,而非复制内核堆栈
    

    要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。
    从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process

    1. *childregs = *current_pt_regs();   //复制内核堆栈
    2.childregs->ax = 0;   //为什么子进程的fork返回0,这里就是原因!
    3.  
    4. p->thread.sp = (unsigned long) childregs;   //调度到子进程时的内核栈顶
    5. p->thread.ip = (unsigned long) ret_from_fork;   //调度到子进程时的第一条指令地址
    
    
    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();//拷贝已有内核堆栈数据和制定新进程的第一条指令地址
    
    childregs->ax = 0;   //子进的程返回值是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;
    

    使用GDB跟踪创建新进程的过程

    可以看到执行结果如上,输入fork输出了一个父进程一个子进程。接下来要使用gdb来跟踪调试进程创建过程,所以需要设置断点。若执行成功,那么可以分别在sys_clone,do_fork,dup_task_struct,copy_process,copy_thread,ret_from_fork这几处设置断点,这是执行一个fork可以看到停在Fork a new process处。按c继续执行:

    下面出现了copy_process

    task_struct结构关系

    struct task_stuct数据结构和庞大,linux进程的状态操作与操作系统原理中的描述状态似乎有所不同,比如TASK_RUNNING既可以表示就绪状态也可以表示运行状态。

    问题与分析


    在实验过程中出现了打不开'initrd'的情况,反复在LinuxKernel和menu目录下尝试几次,结果失败,上网未查到答案,没有解决。

    教材十一章、十二章学习

    1、时间管理在内核中占有很重要的地位,内核中有大量的函数都是基于时间驱动的。事实上内核必须在硬件的帮助之下节拍才能计算和管理时间。硬件为内核提供了一个系统定时器用来计算流逝的时间。系统定时器以某种频率自行触发(也称为“射中” 或 “击中” 时钟中断),该频率可以通过编程预定,称作节拍率。(内核连续两次时钟中断的间隔时间称为节拍,它等于节拍率分之一)当时钟中断发生时,内核就通过一种特殊的中断处理程序对其进行处理。

    2、利用时间中断周期执行的工作:
    更新系统运行时间;跟新实际时间;在smp系统中均衡调度各处理器的运行列队;检查当前进程是否用尽了时间片,如果用尽则重新进行调度;运行超时的动态定时器;更新资源消耗和处理器时间的统计值。

    3、系统定时器的频率(节拍率)是静态预处理定义的。HZ越高的优势:内核定时器以更高的频度和准确度执行;提高运行的精度;对资源消耗和系统运行时间等的测量有更精细的解析度;进程抢占更准确。劣势:节拍率越高,时钟中断程序占用处理器时间越多;并且频繁打乱处理器cache并增加耗电。无时钟OS可以根据系统的空闲程度动态改变频率;全局变量jiffies 用来计算自系统启动以来产生的节拍总数,启动时内核将该初始变量设置为0 ,然后每次时钟中断处理程序会增加这个值.

    4、时钟中断处理程序:
    就是每次发生时钟中断后,需要执行的程序,类似于中断处理程序,已经注册到内核中了,这个程序一般会完成以下的一些工作:获得 xtime_lock 锁,以便对访问 jiffies_64 和墙上时间 xtime 进行保护;需要时应答或重新设置系统时钟;周期性的使用墙上时间更新实时时钟;调用系统结构无关的时钟例程: tick_periodic()。

    5、定时器并不周期运行,动态定时器就是不断的创建和撤销,它的生命周期一般会经历一下几个步骤:

    6、内存最基本的管理是页,同时按照内存地址的大小,大致分为3个区;页是内存管理的最小单元,但是并不是所有的页对于内核都一样,内核将内存按地址的顺序分成了不同的区,有的硬件只能访问有专门的区。一般DMA使用的页物理内存<16MB,分配ZONE_DMA区内存;正常可寻址的页物理内存16~896MB,分配ZONE_NORMAL区内存;动态映射的页物理内存>896MB,分配ZONE_HIGHMEM区内存。linux中的高速缓存是用所谓 slab 层来实现的,slab层即为内核中管理高速缓存的机制。与单CPU环境不同,SMP环境下的并行是真正的并行。单CPU环境是宏观并行,微观串行。真正并行时,会有更多的并发问题。按CPU来分配数据主要有2个优点:最直接的效果就是减少了对数据的锁,提高了系统的性能;由于每个CPU有自己的数据,所以处理器切换时可以大大减少缓存失效的几率。如果一个处理器操作某个数据,而这个数据在另一个处理器的缓存中时,那么存放这个数据的那个处理器必须清理或刷新自己的缓存。持续的缓存失效成为缓存抖动,对系统性能影响很大。

    7、在众多的内存分配函数中,如何选择合适的内存分配函数很重要,下面总结了一些选择的原则:如果需要物理上连续的页,选择低级页分配器或者 kmalloc 函数;如果kmalloc分配是可以睡眠的,指定 GFP_KERNEL标志;如果kmalloc分配是不能睡眠的,指定 GFP_ATOMIC标志;如果不需要物理上连续的页,vmalloc函数;如果需要高端内存,alloc_pages 函数获取page的地址,在用kmap之类的函数进行映射;如果频繁撤销/创建教导的数据结构,建立slab高速缓存。

  • 相关阅读:
    Balanced Binary Tree
    Convert Sorted List to Binary Search Tree
    Convert Sorted Array to Binary Search Tree
    Binary Tree Zigzag Level Order Traversal
    Validate Binary Search Tree
    Binary Tree Level Order Traversal II
    Binary Tree Level Order Traversal
    Maximum Depth of Binary Tree
    如何把U盘的两个盘或者多个盘合成一个
    bugku 想蹭网先解开密码
  • 原文地址:https://www.cnblogs.com/wyz-1995/p/7822471.html
Copyright © 2011-2022 走看看