标签(空格分隔): 20135328陈都node
陈都 原创做品转载请注明出处 《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000linux
最核心的是进程管理编程
将信号、进程间通讯、内存管理和文件系统联系起来数组
为了管理进程,内核必须对每一个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息。
struct task_struct数据结构很庞大
Linux进程的状态与操做系统原理中的描述的进程状态彷佛有所不一样,好比就绪状态和运行状态都是TASK_RUNNING,为何呢?
进程的标示pid
全部进程链表struct list_head tasks;
内核的双向循环链表的实现方法 - 一个更简略的双向循环链表
程序建立的进程具备父子关系,在编程时每每须要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系
Linux为每一个进程分配一个8KB大小的内存区域,用于存放该进程两个不一样的数据结构:Thread_info和进程的内核堆栈
进程处于内核态时使用,不一样于用户态堆栈,即PCB中指定了内核栈,那为何PCB中没有用户态堆栈?用户态堆栈是怎么设定的?
内核控制路径所用的堆栈不多,所以对栈和Thread_info来讲,8KB足够了
struct thread_struct thread; //CPU-specific state of this task
文件系统和文件描述符
内存管理——进程的地址空间数据结构
分析:框架
pid_t pid又叫进程标识符,惟一地标识进程 list_head tasks即进程链表 ——双向循环链表连接起了全部的进程,也表示了父子、兄弟等进程关系 struct mm_struct 指的是进程地址空间,涉及到内存管理(对于X86而言,一共有4G的地址空间) thread_struct thread 与CPU相关的状态结构体 struct *file表示打开的文件链表 Linux为每一个进程分配一个8KB大小的内存区域,用于存放该进程两个不一样的数据结构:Thread_info和进程的内核堆栈
Linux进程的状态与操做系统原理中的描述的进程状态有所不一样,好比就绪状态和运行状态都是TASK_RUNNING
通常操做系统原理中描述的进程状态有就绪态,运行态,阻塞态,可是在实际内核进程管理中是不同的。函数
struct task_struct数据结构很庞大学习
道生一(start_kernel....cpu_idle),一辈子二(kernel_init和kthreadd),二生三(即前面0、1和2三个进程),三生万物(1号进程是全部用户态进程的祖先,0号进程是全部内核线程的祖先),新内核的核心代码已经优化的至关干净,都符合中国传统文化精神了优化
0号进程,是代码写死的,1号进程复制0号进程PCB,再修改,再加载可执行程序。
this
系统调用进程建立过程:
iret与int 0x80指令对应,一个是弹出寄存器值,一个是压入寄存器的值
若是将系统调用类比于fork();那么就至关于系统调用建立了一个子进程,而后子进程返回以后将在内核态运行,而返回到父进程后仍然在用户态运行。
进程的父子关系直观图:
do_fork
fork代码:fork、vfork和clone这三个函数最终都是经过do_fork函数实现的
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char * argv[]) { int pid; /* fork another process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr,"Fork Failed!"); exit(-1); } else if (pid == 0) //pid == 0和下面的else都会被执行到(一个是在父进程中即pid ==0的状况,一个是在子进程中,即pid不等于0) { /* child process */pid=0时 if和else都会执行 fork系统调用在父进程和子进程各返回一次 printf("This is Child Process!\n"); } else { /* parent process */ printf("This is Parent Process!\n"); /* parent will wait for the child to complete*/ wait(NULL); printf("Child Complete!\n"); } }
建立新进程的框架do_fork:dup_thread复制父进程的PCB
long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long nr; p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace); }
copy_process:进程建立的关键,修改复制的PCB以适应子进程的特色,也就是子进程的初始化
static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace) { int retval; struct task_struct *p; // 分配一个新的task_struct p = dup_task_struct(current); // 检查该用户的进程数是否超过限制 if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) { // 检查该用户是否具备相关权限 if (p->real_cred->user != INIT_USER && !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN)) goto bad_fork_free; } retval = -EAGAIN; // 检查进程数量是否超过 max_threads if (nr_threads >= max_threads) goto bad_fork_cleanup_count; // 初始化自旋锁,挂起信号,定时器 retval = sched_fork(clone_flags, p); // 初始化子进程的内核栈 retval = copy_thread(clone_flags, stack_start, stack_size, p); if (retval) goto bad_fork_cleanup_io; if (pid != &init_struct_pid) { retval = -ENOMEM; // 这里为子进程分配了新的pid号 pid = alloc_pid(p->nsproxy->pid_ns_for_children); if (!pid) goto bad_fork_cleanup_io; } /* ok, now we should be set up.. */ // 设置子进程的pid p->pid = pid_nr(pid); // 若是是建立线程 if (clone_flags & CLONE_THREAD) { p->exit_signal = -1; // 线程组的leader设置为当前线程的leader p->group_leader = current->group_leader; // tgid是当前线程组的id,也就是main进程的pid p->tgid = current->tgid; } else { if (clone_flags & CLONE_PARENT) p->exit_signal = current->group_leader->exit_signal; else p->exit_signal = (clone_flags & CSIGNAL); // 建立的是进程,本身是一个单独的线程组 p->group_leader = p; // tgid和pid相同 p->tgid = p->pid; } if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) { //同一线程组内的全部线程、进程共享父进程 p->real_parent = current->real_parent; p->parent_exec_id = current->parent_exec_id; } else { // 若是是建立进程,当前进程就是子进程的父进程 p->real_parent = current; p->parent_exec_id = current->self_exec_id; }
dup_ task_ struct
copy_thread:
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; // 若是是建立的内核线程 if (unlikely(p->flags & PF_KTHREAD)) { /* kernel thread */ memset(childregs, 0, sizeof(struct pt_regs)); // 内核线程开始执行的位置 p->thread.ip = (unsigned long) 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; } // 复制内核堆栈,并非所有,只是regs结构体(内核堆栈栈底的程序) *childregs = *current_pt_regs(); childregs->ax = 0; if (sp) childregs->sp = sp; // 子进程从ret_from_fork开始执行 p->thread.ip = (unsigned long) ret_from_fork;//调度到子进程时的第一条指令地址,也就是说返回的就是子进程的空间了 task_user_gs(p) = get_user_gs(current_pt_regs()); return err; }
#ifdef CONFIG_SMP //条件编译,多处理器会用到 struct llist_node wake_entry; int on_cpu; struct task_struct *last_wakee; unsigned long wakee_flips; unsigned long wakee_flip_decay_ts; int wake_cpu; #endif int on_rq; int prio, static_prio, normal_prio; unsigned int rt_priority; //与优先级相关 const struct sched_class *sched_class; struct sched_entity se; struct sched_rt_entity rt; …… struct list_head tasks; //进程链表 #ifdef CONFIG_SMP struct plist_node pushable_tasks; struct rb_node pushable_dl_tasks; #endif
fork、vfork和clone三个系统调用均可以建立一个新进程,并且都是经过调用do_fork来实现进程的建立;
$ err = arch_dup_task_struct(tsk, orig); //在这个函数复制父进程的数据结构
$ ti = alloc_thread_info_node(tsk, node); $ tsk->stack = ti; //复制内核堆栈 $ setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
$ *childregs = *current_pt_regs(); //复制内核堆栈 $ childregs->ax = 0; //为何子进程的fork返回0,这里就是缘由 $ p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶 $ p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
fork()函数被调用一次,但返回两次
新进程如何开始的关键:
copy_thread()中:
p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
将子进程的ip设置为ret_ form _ fork的首地址,所以子进程是从ret_ from_ fork开始执行的。
在设置子进程的ip以前:
p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
*childregs = *current_ pt_ regs();
将父进程的regs参数赋值到子进程的内核堆栈,*childregs的类型为pt_regs,其中存放了SAVE ALL中压入栈的参数。