分类: 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 中看到:
它的设置在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 换成其他函数而已