zx_wing 发表于 2008-05-07 15:11

原帖由 frank_seng 于 2008-5-7 14:57 发表 http://linux.chinaunix.net/bbs/images/common/back.gif


真的好对不起,由于历史原因,我们的产品依然跑在Linux 2.4上,因此是对着2.4说的,2.6除了网络部分外,还从没看过,sorry啊!

关于COW,我写了一些总结性文档,贴出来献丑了:假设进程A创建了子进程B,之 ...
LZ客气了,讲的很清楚。
也就是说,COW时,不管该页面最初的拥有者是谁,只要还是多个进程拥有,那么谁先写就给谁分配一个新的。最老的页面留给最后单独拥有该页面的进程。
我原来以为是老页面一直留给父进程。LZ讲的这种机制更合理。以前也只是在书上看过COW的机制,但没看过代码,有很多不清楚。
哪位老大知道2.6中的COW代码在哪儿啊,麻烦给个路径,找半天没找到。

不好意思打断了,LZ请继续写,有问题还会问的:mrgreen:

frank_seng 发表于 2008-05-07 15:12

回复 #9 zx_wing 的帖子

如果父子都不可写,父写的时候分配一个新页面给父,那老页面是不是就给子了呢?
答:非常对

如果子先写,分配一个新页面给子,老页面是不是就给父了呢?
答:非常对

COW就是这个机制!

frank_seng 发表于 2008-05-07 16:54

回复 #12 frank_seng 的帖子

sys_exec("/bin/apache") 执行过程

[*]当前进程进入sys_exec系统调用;
[*]以下为load_elf_binary:打开apache文件,读取其128字节头,依据头找到elf处理模块(假设这里apache为elf文件);
[*]确定程序所使用的动态库,一般为/usr/lib/ld_linux.so.2(从apache文件头部分分析出),随后打开该库文件(也就是解释器);
[*]读取解释器的首部;
[*]以下为flush_old_exec(执行该函数之后,调用sys_exec的进程将直接消失,从此与当前进程无关,而是被apache完全代替),为apache日后运行能拥有自己独立的信号,以免影响其他进程,这里为apache创建新的信号表;
[*]释放所有的current->mm->mmap;
[*]释放原来老的信号表(刚刚前面在创建新的信号表时还没有释放老信号表);
[*]以下为setup_arg_pages:为命令行参数args建立相应的vm_area_struct(start=3G-args's size;end=3G),自栈顶往下,由于sys_execv刚开始时就已经将args读取到内核内存中,这里在put_dirty_page函数中为vm_start----- vm_end这段用户空间对应的页目录项创建相应的页表,并设置相应的该页表的页表项指向args所在的物理页面;
[*]以下为 elf_map:为apache中的.code、.data等段建立vm_area_struct,并建立到apache磁盘文件中对应磁盘页面的映射, vm_area_struct的vm_start、vm_end和vm_pgoff已经在apache elf文件中由编译器指明,注意这里没有为对应的页目录表项创建页表,更不存在设置页表项了;
[*]以下为load_elf_interp:为解释器ld_linux.so.2建立vm_area_struct,并建立到该文件的磁盘映射;
[*]重新设置current->mm的参数,简历bss段,设置新的EIP(指向解释器的代码段)和ESP(指向位于栈顶的args参数底部);当sys_exec从内核系统调用中返回到用户空间时,将从该EIP处开始执行;

注意:例如在test程序中调用execve("/bin/apache"),则执行完毕后,test程序就不存在了,被apache完全代替,pid保持不变。

frank_seng 发表于 2008-05-08 09:36

回复 #13 frank_seng 的帖子

0号进程的手工构造过程

其是利用全局变量构造的,如下图所示:

         init_task_union
      ---+-------------+
       ^ |             |
       | |             |
       | |             |
      8K |             |    init_task
       | |             |   ,+------------------------+
       | |             |/ |                        |
       | |             | /| policy:    SCHED_OTHER |
       | +-------------+/   | mm:      NULL      |
       v | task_struct |    | active_mm: init_mm ------+
      ---+-------------+\   | user:      INIT_USER   | |
                         \| .....                  | |
                        \ |                        | |
                           '+------------------------+ |
                                                       |
                        init_mm                        |
                        +-------------------------+<---+
                        |                         |
                        | mmap:    init_mmap      |
                        | pgd:   swapper_pg_dir |
                        | .....                   |
                        |                         |
                        +-------------------------+


[*]ESP刚开始的指向:在head.S中:lss stack_start,%esp设置了esp寄存器,而stack_start已经被定义为SYMBOL_NAME(init_task_union)+8192,很显然,esp在内核启动时指向init_task_union顶部;
[*]current的解释:而current宏则就是按8K宏对esp取整,因此无论esp因压栈出栈导致其在栈中(init_task_union)何位值,都会得到task_struct的首指针;
[*]因此,综上,Linux内核启动后,current也就是init_task,可以理解成当前进程init_task中;
[*]注意,init_task的pid为0;

albcamus 发表于 2008-05-08 11:47

10. fork之后,在哪里设置了copy-on-write的页面只读属性?

      sys_fork() > do_fork() > copy_process() > copy_mm() > dup_mm() > dup_mmap() >
      copy_page_range() > copy_hugetlb_page_range() :

      int cow = (vma->vm_flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWARITE;
                ...
                if (cow)
                        ptep_set_wrprotect(src, addr, src_pte);
                        ...



应该是以前在本版看到的帖子然后记录的笔记。

frank_seng 发表于 2008-05-08 11:56

回复 #15 albcamus 的帖子

elf文件段属性和vm_area_struct关系:

在elf文件段中,有如下属性:

   1. p_type:该成员指出了这个数组的元素描述了什么类型的段,或怎样解释该数组元素的信息。
   2. p_offset:该成员给出了该段的驻留位置相对于文件开始处的偏移。
   3. p_vaddr:该成员给出了该段在内存中的首字节地址。
   4. p_filesz:该成员给出了文件映像中该段的字节数;它可能是 0 。
   5. p_memsz:该成员给出了内存映像中该段的字节数;它可能是 0 。
   6. p_flags:该成员给出了和该段相关的标志。定义的标志值如下所述。
   7. p_align:就象在后面“载入程序”部分中所说的那样,可载入的进程段必须有合适的p_vaddr 、 p_offset 值,取页面大小的模。该成员给出了该段在内存和文件中排列值。 0 和 1 表示不需要排列。否则, p_align 必须为正的 2 的幂,并且 p_vaddr 应当等于 p_offset 模 p_align 。

那么vm_area_struct中相应的成员和elf文件段属性的关系为:

   1. vm_start ---> p_vaddr :该vm_area_struct对应的用户空间区间起始逻辑地址;
   2. vm_end ---> p_vaddr+p_filesz :该vm_area_struct对应的用户空间区间结束逻辑地址;
   3. vm_pgoff ---> p_offset:该vm_area_struct对应的实际数据首字节在文件中的偏移地址;

frank_seng 发表于 2008-05-08 11:57

回复 #16 frank_seng 的帖子

关于schedule和cpu_idle的一些问题:

   1. schedule会从一系列进程中选择最合适的进程来运行,假设某一时刻没有合适的进程(在寻找待切换入进程时next时,先将next设置为init_task,然后在runqueue_head运行队列中找合适的进程,找到,就将其赋给next,注意,init_task不会在runqueue_head队列中,没找到,则就是init_task了),则只能选择 init_task,这样便回到了init_task在cpu_idle->schedule()中被换出时的点,由于此时 init_task.need_resched==0,故进入idle(一般为poll_idle函数,在此函数中, init_task.need_resched被设置为-1),此时CPU halt了,直到中断的到来,CPU才会停止halt;
   2. 如果此时来了个中断,则中断处理完毕后返回前,发现current(也就是init_task)的need_resched为-1(!=0),又会主动调用 schedule,在schedule中,首先将current(也就是init_task)的need_resched清零,然后选择合适的进程来运行,如果找不到合适的,则schedule直接就返回了,此时也就是从中断处理中返回,这样又回到了cpu_idle->idle-> halt被中断时的点,再次进行1->2这样的循环;如果schedule找到了另一个合适的进程,则就切换到该进程去运行了, init_task则被挂起在idle->halt被中断时点的那个位置;

frank_seng 发表于 2008-05-08 14:56

回复 #17 frank_seng 的帖子

#
Linux kernel启动时第一次进入cpu_idle的简单分析,下图为其流程图,从图中可知,由于current(也就是init_task)的need_resched现在等于1,故立刻开始执行schedule();

                |
+-------------->|
| +------------>|
| |             v
| |          _,,-,,_
| |   ,,.-'``       ``'-.,,
| | current->need_resched==0? ---+
| |    `'-.,,      ,,.-'``   |
| |          ``'--'``            | N
| |            Y|                |
| |             v                v
| |      +--------------+ +--------------+
| |      |    idle()    | |schedule()|
| |      +--------------+ +--------------+
| |             |                |
| +-------------+                |
+--------------------------------+



[*]现在就两个进程:init_task和new_task(init),很显然schedule->switch_to会选中new_task开始执行,同时将init_task.need_resched置0;
[*]new_task 的esp、eip在(3)中已经被设置好了,故esp寄存器被恢复为new_task->thread.esp,也就是 union2.pt_regs底部;eip寄存器被恢复为new_task->thread.eip也就是ret_from_fork, new_task从ret_from_fork处开始执行,由于new_task系统堆栈中保存的用于返回的EIP是从原来的init_task中复制过来的,而且new_task->need_resched=0,因此new_task返回到kernel_thread中;
[*]随后在kernel_thread中,此时将esp和esi比较(esi中保存的是刚进kernel_thread时的esp),对于new_task而言,很明显,这里是不相等的,esp为new_task堆栈指针地址,esi为init_task堆栈指针地址;
[*]执行函数init(),在init中会执行execve("/sbin/init")【内核中的execve宏定义见_syscall3】,也就是 sys_excve("/sbin/init),new_task完全被/sbin/init替代,其永远不会再回到kernel_thread了,在 sys_execve中会构造用户空间,/sbin/init也会启动一些列应用层初始化进程,系统主要起始进程启动成功;
[*]关于kernel_thread需要说明的是:如果这里不是init()函数,而是一个普通函数,则kernel_thread在执行完该普通函数后,执行sys_exit退出并释放该任务;

xpl 发表于 2008-05-08 16:43

非常想知道这些图是怎么画出来的.

搂主能否讲讲?

frank_seng 发表于 2008-05-08 17:01

原帖由 xpl 于 2008-5-8 16:43 发表 http://linux.chinaunix.net/bbs/images/common/back.gif
非常想知道这些图是怎么画出来的.

搂主能否讲讲?
Ascii Picture Designer
页: 1 [2] 3
查看完整版本: Linux sys_exec中可执行文件映射的建立及读取