Linux 2.6.12 内核 进程 1 的创建(二)
下面分析一下do_fork() 函数:
`</p>
/* * Ok, this is the main fork-routine. * * It copies the process, and if successful kick-starts * it and waits for it to finish using the VM if required. */ long do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long pid = alloc_pidmap(); 为进程申请进程号pid if (pid < 0) return -EAGAIN; 若申请失败返回一个值为(-EAGAIN)的错误号,表示系统资源不足,请稍后重试 if (unlikely(current->ptrace)) { trace = fork_traceflag (clone_flags); if (trace) clone_flags |= CLONE_PTRACE; } 判断当前进程是否处于被跟踪状态。如果当前进程处于被跟踪调试状态且传进来的实际参数 clone_flags 没有使用 CLONE_UNPTRACED 强调子进程不可被跟踪,那么在标记 clone_flags 中添加标记 CLONE_PTRACE ,使创建的进程也处于被跟踪调试状态! 此时current 为进程0 ,current->ptrace = 0 没有处于被跟踪调试状态! p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid); 完成具体进程创建工作。该函数创建一个但前进程的进程描述符的拷贝(内容不完全相同)作为新创建进程描述符,并将描述符地址返回! /* * Do this prior waking up the new thread - the thread pointer * might get invalid after that point, if the thread exits quickly. */ if (!IS_ERR(p)) { 判断返回的描述符是否正确! struct completion vfork; if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); } if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) { /* * We'll start up with an immediate SIGSTOP. */ sigaddset(&p->pending.signal, SIGSTOP); set_tsk_thread_flag(p, TIF_SIGPENDING); } 若存在以上标志,在新建进程处理链表中添加信号SIGSTOP,并设置进程的有信号处理标记 TIF_SIGPENDING,这样在进程 获得处理器时会立即处理信号 SIGSTOP,此时进程会立即放弃处理器进入睡眠状态! if (!(clone_flags & CLONE_STOPPED)) wake_up_new_task(p, clone_flags); else p->state = TASK_STOPPED; 若含有CLONE_STOPPED 标记则将新创建进程状态设置为 TASK_STOPPED ,说明新创建的进程不会运行,除非有进程通过信号唤醒它! 若没有 把当前进程添加到当前处理器队列中! wake_up_new_task() 函数最后分析! if (unlikely (trace)) { current->ptrace_message = pid; ptrace_notify ((trace << 8) | SIGTRAP); } if (clone_flags & CLONE_VFORK) { wait_for_completion(&vfork); if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP); } 若包含 CLONE_VFORK ,则将当前进程添加到新创建子进程的进程描述符中成员变量 vfork_done 包含的等待队列中,在子进程退出或通过系统调用 exec() 执行其他程序时,激活该等待队列中的进程! } else { 若失败释放到申请的进程描述符 free_pidmap(pid); pid = PTR_ERR(p); } return pid; 返回 进程号 }`
static inline long IS_ERR(const void *ptr) { return IS_ERR_VALUE((unsigned long)ptr); } #define IS_ERR_VALUE(x) unlikely((x) > (unsigned long)-1000L)
若描述符地址(线性地址)大于FFFFFC18 则说明描述符创建错误!
书上说此处有一个书写bug !在内核初始化内存时,在0xFFFFF000 ~ 0xFFFFFFFF之间保留了一个物理页面大小的物理空间用于捕捉内核错误 (unsigned long)-1000L 应该为 (unsigned long)-0x1000L!(因为 -0x1000 才是 0xFFFFF000)
Linux 2.6.12 内核 进程 1 的创建(一)
由进程0创建的内核线程执行init() 函数,init()函数依次完成内核初始化。init()调用execve()系统调用装入可执行程序 init。结果,init内核线程变为一个普通进程,且拥有自己的每进程(per-process)内核数据结构。在系统关闭之前,init进程一直存活,因为它创建和监控在操作系统外层执行的所有进程的活动!
现在让我们进入代码:
`</p>
rest_init() -> kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND)`
kernel_thread 用来启动一个内核线程,fn 表示要执行函数指针,arg 为参数
`</p>
/* * Create a kernel thread */ int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) { struct pt_regs regs; 此结构用于保存进程硬件上下文。 memset(®s, 0, sizeof(regs)); regs.ebx = (unsigned long) fn; regs.edx = (unsigned long) arg; regs.xds = __USER_DS; regs.xes = __USER_DS; regs.orig_eax = -1; regs.eip = (unsigned long) kernel_thread_helper; regs.xcs = __KERNEL_CS; regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0x2; /* Ok, create the new process.. */ return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); 核心函数!标志flags中 CLONE_VM 表示共享地址空间,CLONE_UNTRACED 表示不被跟踪调试 }`
以上代码手工设置了一个假的硬件上下文,因为硬件上下文并不对应于一个真实存在的一个进程。在后面调用函数 do_fork() 时会根据该假的硬件上下文和当前内核线程的其他上下文信息组合起来创建一个进程描述符,也就是说函数 do_fork() 会依照这个假的硬件上下文创建一个内核线程,此时新创建内核线程的进程描述符并不是当前内核线程的进程描述符的拷贝!
在之后的代码中,会将此结构复制到新建内核进程的内核栈中,此结构被模拟为从用户态的系统调用进入内核态。当新建进程被 schedule() 后运行时,iret 指令会从栈中弹出regs.eip,regs.xcs 到 寄存器 eip 和 cs 中,开始真正执行进程 1 的代码!(从kernel_thread_helper()处执行!kernel_thread_helper()函数暂不分析!)
pt_regs 结构:
`</p>
/* this struct defines the way the registers are stored on the stack during a system call. */ struct pt_regs { long ebx; long ecx; long edx; long esi; long edi; long ebp; long eax; int xds; int xes; long orig_eax; long eip; int xcs; long eflags; long esp; int xss; }`
Write and Submit your first Linux kernel Patch
How to write and submit Linux kernel patch ? Greg will tell you . Maybe this video(from youtube) will useful for you!
httpv://www.youtube.com/watch?v=LLBrBBImJt4
Linux 内核 进程 0 (一)
所有进程的祖先叫做进程0,idle 进程(swapper进程),它是在Linux 的初始化阶段从无到有创建的一个内核线程。这个祖先进程使用静态分配的数据结构(所有其他进程的数据结构都是动态分配的)。
下面着重介绍一下进程0 内核栈的初始化过程!
在head.S 中:
`</p>
lss stack_start,%esp …… ENTRY(stack_start) .long init_thread_union+THREAD_SIZE .long __BOOT_DS `
表示加载内核栈,ss= __BOOT_DS,表示内核数据段,而 esp 指向从init_thread_union 开始的大小为THREAD_SIZE的内存处。
(init_thread_union 在C 代码中为初始化的全局变量,在汇编中被当作为label 也就是地址)
那么init_thread_union 是什么呢?
在 init_task.c中:
`</p>
union thread_union init_thread_union __attribute__((__section__(".data.init_task"))) = { INIT_THREAD_INFO(init_task) };`
表示在内核的 .data.init_task 段定义一个共用体变量,并为其初始化。按照THREAD_SIZE 对齐!!
thread_union 为:
`</p>
union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; };`
THREAD_SIZE 定义为 4096
在.data.init_task 段中开辟了4096 字节大小空间用于内核栈。低地址处存放thread_info 结构,剩下的作为内核栈。
让我们想象一下现在内存中栈的情况:
此时 esp 位于下一个4KB 内存单元的开始处,此时栈为空。(push 是先减减esp哦!)因为进程0的数据结构由静态初始化,此时就可以获得当前进程(进程 0) current 进程描述符指针。
`</p>
#define current get_current() static inline struct task_struct * get_current(void) { return current_thread_info()->task; } static inline struct thread_info *current_thread_info(void) { struct thread_info *ti; __asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1))); return ti; }`
因为此时内核栈并不为空,所以很容易就可以算出 thread_info 结构的起始地址,因为在一页内存中,esp 与 FFFFF000 相与 就可算出其基地址既thread_info 结构地址。thread_info 结构的第一项就为其指向tss_struct 的指针,所以很容易就可得到current!
PS:众所周知,内核中的每个进程都有它自己的内核栈(alloc_thread_info()),那么怎么根据 task_struct 获得 进程的内核栈指针呢?(在高版本的内核中)接下来会有一篇文章用一个实例来介绍!
wordpress I am coming
经过一天终于完成 LAMP的源码配置并且成功搭建起 wordpress。终于有了属于自己的空间,简直太棒了!接下来会将 CU 上的博文迁移过来。。。。。。