一. 基本:
- task_struct : 定义在<linux/sched.h>, 大小约1.7K, 包含管理进程需要的所有信息 。 如:进程状态 -> state ; 打开的文件 -> files_struct *files; 进程地址空间 -> ; 信号 -> signal_struct *signal
- Current指针:获取当前进程。在x86里面,寄存器少,是通过先偏移到current_thread_info,再current_thread_info->task; 而current_thread_info就是: andl esp, 8192,回到当前栈末尾;因为thread_info在x86里面就存在末尾;
- State:当前状态,用set_task_state(pid, state)可以设置,状态有running, ready, interuptable
- 遍历进程树: list_entry(task->tasks.next, struct task_struct, tasks) 就可以遍历了, 明显 ->tasks是双向list_node
- fork() : 内核 = do_fork(),
do_fork() = copy_process() + wake_up_new_task(); 即先copy一个process, 再叫醒它; wake_up => activate_task => enqueue_task;
线程:在内核里面,与进程的实现方式类似;
二. 调度:
Linux是抢占式的。其优先级分实时和非实时。
- Nice值 & 实时优先级; 实时进程总是优先于普通进程; ps -lx, NI对应的列就是nice值;
nice值越小,在一个circle内进程执行时间片越长; 为 -20 - 19之间.
用 nice 和 renice命令能够改变默认值。
- Prio : ps -eo state,uid,pid,ppid,rtprio,time看实时进程;
- CFS公平调度: 在一个周期内,每个进程运行一次;运行时间为 nice / circle; circle越大,切换消耗越小,但是实时性降低;
实现: kernel/sched_fair.c
task_struct -> (sched_entry)se 结构; 包含sum_exec_runtime, start_runtime, vruntime, 分别是总运行时间, 起步时间, 比例运行时间;
update_curr()函数来更新,以下是三种常见情况:
=> schedule() -> dequeue_task() -> dequeue_entity() 进程出列
=> wake_up_process() -> enqueue_task() -> enqueue_entity() 进程入列,这里enqueue_entity时会按照vruntime来排序成2叉树;
=> hrtimer_interrupt() -> tick_sched_timer() -> entity_tick() tick中断
- schedule调度: 实现在sched.c,在cpu_idle()时会调用; 找到最高优先级类中,该运行的下个task;
- 休眠队列, 通过一系列动作实现: DEFINE_WAIT()生成队列; add_wait_queue()把自己加入对列; prepare_to_wait()开始等待; 直到有其它地方wake_up()
三. 进程的抢占
a. 每个进程都有need_resched bit位;该标志作用是:告诉内核,是否需要call schedule()来抢占了;
中断返回 & 返回用户态 & 进程阻塞(直接调用schedule) &cpu_idle 都会引起调度;
b. 抢占还需要判断锁,preempt_count,为0才可以抢占;
四. 进程的内存空间
a. 地址空间:每进程一个,32位平坦的寻址范围。即使两进程有相同内存地址,也不相干... ?? , mm_struct
b. 内存区域: 进程能访问的地址空间,可以通过映射(mmap)来动态增加。访问越界会有段错误。mm_struct -> (vm_area_struct *)mmap
c. 地址段: 内存区域分段,代码 - 全局数据 - bss - 用户栈 - 内存映射文件 - 匿名映射 (堆)
fork:
父进程会copy_process => copy_mm(),给子进程一样的地址空间。此时当前进程的3GB~4GB的内核态虚拟地址对应的页表项被复制到新进程的页表项中,所以说所有进程共享1GB的内核态地址空间。但是对于用户态虚拟地址区域,则把它的进程页表项设置为只读,这样当其中一个进程对其进行写入操作时,do_page_fault()会分配新的物理页面,并建立映射,从而实现Copy On Write机制。这样父子可以读到一样的内存,但是子进程写时会新开物理page。
如果加上了clone_vm标识,tsk->mm = current->mm, 就变成线程了,共享地址空间。
缺页机制:
页错误有三种:虚地址无效;虚地址有效但没映射;虚地址被保护,不能写。缺页机制就是第二种。
假设虚拟地址域有8K,进程最初仅仅从起始虚拟地址A开始映射了一个物理页面,当该进程首次访问大于A+4KB小于A+8KB的虚拟地址时,由于这个虚拟地址没有映射物理页面,因此会触发页面故障,此时缺页中断处理函数do_page_fault()将查找该进程的vm_area_struct结构,在本例中,这个被访问的虚拟地址在合法的范围内,所以do_page_fault()会分配一个新的物理页面并为这个虚拟地址建立映射,此时该进程成功的访问到第二个物理页面。
do_page_fault() => handle_mm_fault() => handle_pte_fault()