Chinaunix首页 | 论坛 | 博客
  • 博客访问: 405347
  • 博文数量: 53
  • 博客积分: 1910
  • 博客等级: 中尉
  • 技术积分: 1130
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-10 14:56
文章分类

全部博文(53)

文章存档

2013年(1)

2012年(17)

2011年(33)

2010年(2)

分类: LINUX

2011-04-29 17:26:53

1. 关于thread_info
   thread_info  本来就是arch 相关的
   但 arm oklinux thread_info 与arm native linux(下称nlinux)
   thread_info
   差别比较大,添加了一些和l4 相关的特性
   struct thread_info {
 L4_ThreadId_t           user_tid; /* user process L4-thread Id */
 L4_ThreadId_t           user_handle; /* handle to check incoming IPC tid with read tid */
                                         /* 这个handle 在  okl4_create_thread 获得 */

 struct task_struct *task;  /* main task structure */
 struct exec_domain *exec_domain; /* execution domain */

 unsigned long  flags;  /* low level flags */
 unsigned long  irq_flags; /* irq flags over context switch */
 __u32   cpu;  /* current CPU */
 __s32   preempt_count;  /* 0 => preemptable, <0 => BUG */

 struct restart_block    restart_block;

 struct arch_kernel_context context;/* architecture independent kernel context */
 struct pt_regs  regs;  /* user process saved registers */
 L4_MsgTag_t  tag;  /* user process L4 tag */
 /*
  * The tls bitmap is only used on the i386 architecture
  * at this point in theory could be used to implement
   * multiple slots for the thread local storage area.
  *
  * -gl
  */
 unsigned long  tls_bitmap;
 unsigned long  tp_value;

#define OP_NONE  0
#define OP_FORK  1       /* 指定fork 操作*/
#define OP_KTHREAD 2       /* 指定创建内核线程*/
#define OP_DELETE 3
#define OP_RESUME 4
 struct {
  long op;
  union {
   struct {
    L4_Word_t user_ip;
    L4_Word_t user_sp;
    L4_Word_t user_start;
   } fork, exec;
   struct {
    int (*proc)(void *);
    void *arg;
   } thread;
   struct {
    void (*proc)(void *);
    void *arg;
   } cb;
  } u;
 } request;
 struct {
  L4_Word_t user_ip;
  L4_Word_t user_sp;
  L4_Word_t user_flags;
 } restart;
};
    
 user_tid   : 用于保存l4 thread 的id (就是cap)
 user_handle: 是在l4 creat thread 时获得的 thread handle
              是用ktcb的tcb_index 创建一个capid_t 类的对象,
              其实thread handle  就是 tcb_idx
              注意thread id 是TYPE_CAP , thread handle 是TYPE_THREAD_HANDLE
 context:     对应arm oklinux 保存r4 - pc 的arm reg
 regs   :     原来在内核栈上保存的进程进入kernel mode 时的寄存器的值
              现在保存在regs 上,reg 中的mode 保存了进程的mode
              /* mode: 0 - in user, 1 - in kernel, 2 - isr */
 tag:         放了接收到ipc msg 的tag ,通过L4_Label,可以知道tag
              类型,然后在进程进入kernel mode 时用于判断什么原因
              进入kernel mode
                           
 tls_bitmap:  x86 使用,飘过
 
 request:    和进程创建的fork 有关, 对于用户进程fork,或者kthread 创建
             使用union 中不同的struct
             具体copy thread  分析时再说
 restart:    保存重调度运行时的pc,sp


2.  fork 前
    主要是指创建kernel thread, 和实际fork 的一些差别
   首先看下kernel thread 创建
    通过直接设置request  op 为 OP_KTHREAD,并指定union中thread 结构中函数指针
     和参数
    
   接下来是sys_fork,sys_vfork 这部分基本类似,
      但要指定 requrest op 的值为  OP_FORK,给do_fork 中
      copy_thread 做判断,做相应操作
   
   接下来是sys_clone,因为navite sysclone 如下
   asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp,
    int __user *parent_tidptr, int tls_val,
    int __user *child_tidptr, struct pt_regs *regs)
   所以要将parent_tidptr ,child_tidptr,从regs 中r2, r4 取出,做为do_fork 时
   传入


3. do_fork 过程
   首先oklinux 中do_fork 的流程和nlinux 一样
  
   其中copy_process中copy_mm 执行过程一样,但主要区别在于
   mm_struct 中mm_context_t 有变化
   mm_context_t 在支持ASID 的arm 上用来存放地址空间ID,
   而oklinux 中 ASID ,或者FSCE等硬件部分都是由l4 来管理
   对应上层,代以space id ,
   所以 mm_context_t,中对于space id 添加了L4_SpaceId_t space_id
   typedef struct {
 L4_SpaceId_t space_id;     /* l4 地址空间id !!! */
#ifdef ARM_PID_RELOC /* XXX should move to asm-l4/arm/mmu.h -gl */
 int pid;
 unsigned largevm:1;
 rfl_t mmap_free_list;
#endif
 L4_ARCH_MMU_CONTEXT
} mm_context_t;

    ARM_PID_RELOC 于arm9 fass 相关
   
    init_new_context 也发生相应变化,主要执行过程:
    先在space_caches上获得1个space id 如果没有
    通过okl4_create_space 在spaceid_pool获得一个
    spaceid_pool 是在    l4_process_init
    通过okl4_env_get("MAIN_SPACE_ID_POOL")取得
   
    其中space 设置最大 256个
    可以从wearver.xml 中看到:
        kernel_heap="0x400000" mutexes="256" name="oklinux" spaces="256">
    它的设置在cell 的linux Sconscript 中如下:
    rootserver_env.set_cell_config(spaces = 256, clists = 256, mutexes = 256)
   
   
    接下来dup_mmap,和nlinux 一样,进行虚拟内存mmap 的copy ,以及页表的 copy
    如前面pagefualt 文中所说,这里的linux 页表是shadow的概念,不在硬件mmu上
    起作用,但是对于涉及到实际mmu 上个对页属性的改变部分,比如COW,要设置
    写保护,那么完成shadow 页表的设置还需要call l4 map api 去重新map
    该page , 关于oklinux pagetable  操作价构独立部分内容在
    include\asm\l4\pgtable.h中
    其实上面COW 所说的ptep_set_wrprotect,在其中多了个tlb_modify
    该函数就是通过call l4 map api 实现从新map ,修改page 属性
   
   
    最后是copy_thread了,在nlinux  中比较简单,
    1.在子进程内核栈上造出pt_regs,然后copy 父进程的pt_regs
    2.然后修改r0 =0 ,就是表示子进程从fork 系统调用返回0
    3.子进程,用户态栈的设置(根据传入参数),具体可看 pthread_create到ret_fast_syscalls
      参数和栈的变化一文
    4.设置cpu_context,主要是当子进程被调度运行时的reg,栈,以及执行的pc地址
    5.设置tls 的地址
   
    而对于oklinux 这部分功能相同,但是实现方法不同,因为oklinux中
    一切都是 l4 thread ,没有nlinux 进程的概念,也没正真的kernel
    mode ,user mode 和其对应的栈,kernel 和user 都运行在l4 kernel
    的外面,对l4 kernel 来讲大家都是user,下面看下具体实现:
   
    oklinux copy_thread 中主要有两个分支, 就是判断fork ,还是
    kthread 创建,判断条件就是前面request.op flag设置的OP_KTHREAD
    OP_FORK,对于OP_FORK 情况
    1.取得utcb_area地址, armv4,5 由l4 kernel 管理
      armv6 以上自己分配,具体实现在arch\l4\kernel\setup.c 中setup_arch
     
    2.算出fork_start 的位置,就是子进程被调度运行时执行的函数
      根据TASK_SIG_BASE 和 __wombat_user_fork_handler,并且如果
      是armv6,那么fork_start =0x98000004
     
    3. 然后 通过okl4_create_thread  创建l4 thread,返回ktcb的tcb_idx
       到thread_info 的 user_handle,并且返回l4 thread id
      因为oklinux ,运行的实际单位是 l4 thread,而不是nlinux 的进程
      其中pager,和schduler都是main_thread,即syscall_loop所在的
      执行线程
     
    4. 设置刚创建的 l4 thread 优先级
   
    5. 使用l4 api L4_Copy_regs,将当前进程的所有reg copy到
       子进程中
      
    6. 接下来没有设置子进程获得执行时运行的sp,pc到cpu context
       而是设置到fork request  的user_sp,user_ip,
       另外多了个user_start,设置为fork_start,
      
    7. 接下来如注释 :/* Start new user thread - at new_process_handler stub  */  
       就是先设置request op flag 为resume,将thread info中cpu context中
       pc 设置为new_process_handler,其实这个new_process_handler,
       就是执行fork_start的stub, l4 thread 的sp 设置为nlinux中kernel 栈的2page 的
       顶端位置
      
       在发生调度,通过__switch_to =>arch_switch ,实际切换执行到cpu context
       的pc 也就是 new_process_handler,然后执行到 fork_start
       看下new_process_handler,他根据l4 thread id 将thread 停下来
       然后设置restart 的sp,pc 为 thread_info 中reqeset的user_start就是fork_start
      
       然后set_user_ipc_cancelled,将thread_info flag TIF_DONT_REPLY_USER标志
       clear 掉,他的作用 下面讲 返回到user mode 时说
       最后  call syscall_loop
      
         
   
4. 返回到用户mode
   nlinux 创建进程返回到用户空间的代码是真刀真枪的,设置cpsr,
   现场恢复,pc出栈,可是现在oklinux kernel 和user 都相对l4 kernel
   的user mode
   正真的kernel mode 在l4 kernel 不在 oklinux kernel ,怎么办
   只能造假,就是装,比如你是个平民百姓,不会有人觉得你很真实
   但是如果你演好了一个平民百姓,你就是影帝,说明要装得象要了解

   平民百姓的本质,就是养家糊口

   来看下oklinux 是如何了解mode 切换的本质的,并且如何演的
  
   还是syscall_loop:
     goto return_to_user ,很直接
    
     返回user mode 先要enable 中断,和nlinux 一样
     接下来判断reply_user_ipc
     前面new_process_handler 中set_user_ipc_cancelled
     起作用了,所以if (likely(reply_user_ipc(curinfo))) 不满足
     走到else :
     先判断user_need_restart,肯定满足,new_process_handler
     做了set_need_restart
     那么就call l4 api L4_Start_SpIpFlags,设置该tid 的l4 thread
     sp,pc(ip) 到reqeset restart 字段的 sp,ip,前面new_process_handler
     set_need_restart,设置为request fork字段的 user_sp,和
     user_start(fork_start)
     接着
     curinfo->regs.mode = 0;
     然后tag = L4_Wait (&from);
    
     结束了,是的就这么简单,kernel mode 和user mode 的本质是什么
     就是谁获得运行权,现在L4_Wait 导致 main_thread 让出运行权
     引发调度,那么user mode 下的thread,谁的优先级高谁运行,
     但不管怎样轮到user thread运行了,就是回到user mode了
    
     轮到父进程的thread 运行,父进程就回到user mode
     轮到fork 出的子进程的l4 thread 运行,子进程就回到了user mode

 


5.fork_start 做了什么
      曾经在oklinux arm pagefault 流程简单分析一文
      中提到过关于TASK_SIG_BASE(0x98000000) 处pagefault 的情况
      这个地址和信号处理有关,也和fork 有关, 在user.S中有个
      .section .exregspage ,vmlinux.lds中定义为1page 大小
      在该段中有很多函数,我们这里只关心__wombat_user_fork_handler
      它就是fork_start,
      当执行fork_start 时地址在0x98000004,该处地址在当前地址空间
      没有被map,就是没有具体的物理地址,引发page fault ,
      page fault 处理将0x98000000 map到__user_exregs_page
      他也是个虚地址,但没关系,因为他在vmlinux 的text段
      init 时 被map 过了,有实际物理地址,这样okl4_find_segment
      可以获得,然后将0x98000000 1page 也map 过去
      这样就可以执行了
      为什么这样做,因为在不同的地址空间,所有要回到user thread的地址空间运行
      必须map 过去,看下 oklinux main thread的创建:
            main_thread =okl4_create_sys_thread(linux_space,
      在linux_space 中的, 其他oklinux user thread , 如上面init_new_context
      是不同的space
      (linux_space = L4_SpaceId(*(OKL4_ENV_GET_MAIN_SPACEID("MAIN_SPACE_ID")));)
     
      下面看 __wombat_user_fork_handler
      get User defined handle ,
      设置 R0 =0 ,熟悉吧,就是子进程从do_fork 返回0
      设置pc 为User defined handle ,继续执行
     
      至于__L4_TCR_USER_DEFINED_HANDLE,的设置可以在3.0的Tarps.spp中找到一点影子
     
      /* Set new UTCB XXX - if we fault after this, (before switch) is this bad? */
      ldr     tmp5,   [to_tcb, #OFS_TCB_UTCB]
      ...... 
      str     tmp5,   [tmp2, #0xff0]          /* UTCB ref */
      但具体含义占个位置,以后继续迭代

 

最后补充下kthread,这部分很简单,没有设置fork_start,然后stub 是new_thread_handler
并且也没有新建l4 thread ,不需要,还是main_thread,只是pc 换成其他函数而已

阅读(2028) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~