目录
1、thread_info和task_struct
thread_info保存了线程所需的所有特定处理器的信息, 以及通用的task_struct的指针。task_struct结构包含的数据能够完整地描述一个正在执行的才会程序。
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
...
}
task_struct 结构包含了很多描述进程属性的东西,近千行的变量定义。
/*
* low level task data that entry.S needs immediate access to.
* __switch_to() assumes cpu_context follows immediately after cpu_domain.
*/
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu context */
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value;
struct crunch_state crunchstate;
union fp_state fpstate __attribute__((aligned(8)));
union vfp_state vfpstate;
struct restart_block restart_block;
};
2、设置进程状态
set_task_state(task,state)
3、初始化链表
LIST_HEAD(xx_list); //定义并初始化xx_list链表。或者,
struct list_head xx_list; // 定义一个链表
INIT_LIST_HEAD(&xx_list); // 使用INIT_LIST_HEAD函数初始化链表 ↩︎
4、fork的调用
fork()
------>clone()
------ ------>do_fork()
------ ------ ------>copy_process()
------ ------ ------ ------>dup_task_struct() //创建内核栈、thread_info和task_struct
------ ------ ------ ------>copy_flags() //更新task_struct的flags成员
------ ------ ------ ------>alloc_pid() //分配有效PID
------ ------ ------>wake_up_new_task() //加入调度器
5、进程终结
exit() //正常退出或者异常退出
------>do_exit()
------ ------>del_timer_sync() //删定时器
------ ------>acct_update_integrals() //BSD的进程记账输出
------ ------>exit_mm() //释放占用的mm_struct
------ ------>sem_exit() //退出IPC信号等待队列
------ ------>exit_files() //递减文件描述符的引用计数
------ ------>exit_fs() //递减文件系统数据的引用计数
------ ------>exit_notify() //向父进程发送信号
------ ------ ------>forget_original_parent() //如果父进程先于子进程终结
------ ------ ------ ------>find_new_reaper() //为子进程寻找新的父进程
------ ------>schedule() //切换到新的进程,不会返回
6、删除进程描述符
进程终止时所需的清理工作和进程描述符的删除被分开执行的。
release_task()
------>__exit_signal() //释放目前僵死进程所使用的所有剩余资源,并进行最终统计和记录。
------ ------>_unhash_process()
------ ------ ------> detach_pid() //从 pidhash 上删除该进程,同时也要从任务中删除该进程。
------>put_task_struct() //释放进程内核栈和thread_info结构所占的页,并释放task_struct所占的slab高速缓存。
7、时间记账
7.1、调度器实体结构
CFS使用调度器实体结构(定义在文件<linux.sched.h>的struct_sched_entity中)来追踪进程运行记账。
struct sched_entity {
struct load_weight load; /* for load-balancing */
struct rb_node run_node;
unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 prev_sum_exec_runtime;
...
}
调度器实体结构是一个名为se的成员变量,嵌入在进程描述符task_struct 内。
7.2、虚拟实时vruntime
vruntime变量存放进程的虚拟运行时间,定义在kernel/sched_fair.c 文件中的update_curr() 函数实现了记账功能:
update_curr()
------ ------>__update_curr()
8、进程选择
CFS使用红黑树来组织可运行进程队列,并利用其迅速找到最小vrumtime值的进程。
8.1、CFS挑选下一个任务
CFS的进程选择算法可以简单的理解为:运行rbtree树中最左边叶子结点所代表的那个进程。__pick_next_entity() 函数实现了这个功能。
__pick_next_entity()
8.2、向树中加入进程
enqueue_entity()
------ ------>__enqueue_entity() //真正进行的插入操作函数
8.3、从树中删除进程
dequeue_entity()
------ ------>__dequeue_entity() //真正进行的删除操作函数
9、调度器入口
schedule()
------ ------>pick_next_task()
------ ------ ------>class.pick_next_task() //选中的最高优先级调度类中的pick_next_task()函数
------ ------ ------ ------>pick_next_entity()
------ ------ ------ ------ ------> __pick_next_entity //挑选下一个进程任务用到的函数
10、睡眠和唤醒
10.1、等待队列
DEFINE_WAIT(); //创建一个等待队列的元素
add_wait_queue(); //加入等待队列
prepare_to_wait(); //改变进程状态为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE。
finish_wait(); //移出等待队列。
10.2、唤醒
wake_up();
------ ------>try_to_wake_up(); //将进程设置为TASK_RUNNING状态。
enqueue_task(); //将进程放入执行红黑树中
11、上下文切换
schedule()
------ ------>context_switch()
------ ------ ------ ------>switch_mm() //把虚拟内存从上一个进程映射切换到新进程中。
------ ------ ------ ------>switch_to() //从上一个进程的处理器状态切换到新进程的处理器状态。
12、调度相关的系统调用
nice() //设置进程的nice值
sched setscheduler() //设置进程的调度策略
sched getscheduler() //获取进程的调度策略
sched_setparam() //设置进程的实时优先级
sched_getparam() //获取进程的实时优先级
sched_get_priority_max() //获取实时优先级的最大值
sched_get_priority_min() //获取实时优先级的最小值
sched_rr_get_interval() //获取进程的时间片值
sched_setaffinity() //设置进程的处理器的亲和力
sched_getaffinity() //获取逬程的处理器的亲和力
sched_yield() //暂时让出处理器
13、系统调用处理程序
用户空间程序
------ >C库调用
------ ------ >引起异常
------ ------ ------ >专门处理系统调用的异常中断处理函数
------ ------ ------ ------ > 利用system_call_table和其他索引信息定位系统调用
------ ------ ------ ------ > 执行系统调用
------ ------ ------ > syscall_exit,系统调用返回后,执行syscall_exit
------ ------ >resume_userspace,返回用户空间
------ >C库
用户空间程序
14、中断
request_irq() //注册一个中断线对应的中断处理函数
free_irq () //释放一个中断线的中断处理函数
local_irq_disable(); //禁止全局中断
local_irq_enable(); //激活全局中断
local_irq_save(flags); //保存当前中断状态,随后禁止中断
local_irq_restore(flags); //恢复中断前的状态
disable_irq(unsigned int irq); //禁止给定中断线,使用前要确保该中断线上没有中断处理函数运行
disable_irq_nosync(unsigned int irq); //禁止给定中断线
enable_irq(unsigned int irq); //激活给定中断线
synchronize_irq(unsigned int irq); //等待给定中断线的中断处理函数退出
irqs_disabled() //查看本地处理器上的中断系统是否被禁用
in_interrupt() //查看内核是否处于中断处理中(包括下半部的处理)
in_irq() //查看内核是否处于中断处理函数中
14.1、中断处理机制
硬件产生中断
------ >中断控制器
------ ------ >处理器
------ ------ ------ >预定义的中断处理入口
------ ------ ------ ------ >do_IRQ() //应答中断,禁用中断线,查找中断处理函数
------ ------ ------ ------ ------ >handle_IRQ_event() //调用中断处理函数
------ ------ ------ ------ ------ ------ >ret_from_intr() //检测need_resched标志和perrmpt_count来决定是否要调用schedule() ,如果不需要调度或者调度返回后,则恢复原先进程的现场。
14.2、/proc/interrupt
/proc/interrupt 文件存放系统中与中断相关的统计信息。
15、下半部
15.1、软中断
struct softirq_action; //软中断数据结构
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp; //编译时静态创建,数组形式,最多32个软中断
void softirq_handler(struct softirq_action *); //处理函数,存放在软中断数据结构的action成员中
执行软中断:
do_softirq();
------ >pending = local_softirq_pending(); //局部变量保存软中断位图
------ >set_softirq_pending(0); //清空软中断位图
------ >h=softirq_vec;h->action(h); //softirq_vec是软中断数组,do_softirq()函数中会检查pending软中断位图,然后调用action处理函数
软中断类型定义在<linux/interrupt.h>文件中,一个枚举表示:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
SCSI_SOFTIRQ,
TASKLET_SOFTIRQ
};
注册软中断处理函数,第一个参数为枚举定义的类型,第二个为中断处理函数,会传递给软中断数据结构的action成员。
open_softirq(NET_TX_SOFTIRQ,net_tx_action);
触发(调度)软中断,相当于放在一个就绪队列,等待do_softirq()函数调用。
raise_softirq(NET_TX_SOFTIRQ);//未禁用中断时使用
raise_softirq_irqoff(NET_TX_SOFTIRQ);//已禁用中断时使用,通常在中断处理函数结束时调用。
15.2、tasklet
tasklet其实就是软中断中的两个类型:HI_SOFTIRQ和TASKLET_SOFTIRQ
struct tasklet_struct //tasklet数据结构
tasklet_vec //由tasklet_struct结构体组成的普通tasklet链表
tasklet_hi_vec //由tasklet_struct结构体组成的高优先级tasklet链表
tasklet_schedule(); //用于调度普通tasklet
tasklet_hi_schedule(); //用于调度高优先级tasklet
DECLARE_TASKLET(name, func, data); //静态创建tasklet,创建完tasklet处于激活状态
DECLARE_TASKLET_DISABLED(name, func, data); //静态创建tasklet,创建完tasklet处于禁用状态
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data); //动态创建tasklet
tasklet_disable(); //禁止tasklet,如果处理函数正在执行,会等待处理函数执行完毕
tasklet_disable_nosync(); //禁止tasklet,不等待处理函数执行完毕
tasklet_enable(&my_tasklet); //激活tasklet
tasklet_kill(); //从挂起的队列中去掉一个tasklet,会引起休眠,禁止在中断上下文使用
15.3、ksoftirqd
当大量软中断出现的时候,内核会唤醒一组内核线程(ksoftirqd/n线程)来处理这些负载。
15.4、工作队列
events/n //缺省的工作者线程叫做events/n ,n代表处理器编号,每个处理器只有一个缺省工作者线程。
struct workqueue_struct //工作者线程类型数据结构
struct cpu_workqueue_struct //工作者线程数据结构
struct work_struct //工作数据结构,通常多个工作会形成链表
DECLARE_WORK(); //静态创建work_struct结构体
INIT_WORK(); //态初始化一个指向work_struct结构的指针
schedule_work(); //把给定的工作处理函数提交给缺省的events工作线程
schedule_delayed_work(); //延迟一定时钟节拍后,把给定的工作处理函数提交给缺省的events工作线程
flush_scheduled_work(); //函数会一直等待events队列,直到队列中所有的对象都被执行完后才返回
create_workqueue(); //创建一个新的工作队列类型
queue_work(); //将工作加入指定类型的工作队列
queue_delayed_work(); //延迟一定时钟节拍后,将工作加入指定类型的工作队列
flush_queue(); //函数会一直等待指定队列,直到队列中所有的对象都被执行完后才返回
local_bh_disable(); //禁止所有下半部
local_bh_enable(); //激活所有下半部
调度流程
工作者线程
------ >worker_thread()
------ ------ >run_workqueue(cwq); //调用run_workqueue执行推后工作
------ ------ ------ >list_entry(cwq->worklist.next,struct work_struct,entry); //获取work_struct对象
------ ------ ------ >f = work->func; //获取work_struct对象上的处理函数func
------ ------ ------ >list_del_init(cwq->worklist.next); //将该节点从链表上删除
------ ------ ------ >work_clear_pending(work); //清除待处理标志位pending
------ ------ ------ >f(work); //调用处理函数
16、自旋锁、信号量、互斥体、完成变量、顺序锁、内核抢占和屏障API
相应比较多,不浪费时间一个个搬过来了,直接参考《笔记第七篇》
17、时间管理
时钟中断处理程序
------ >获得xtime_lock锁
------ >tick_periodic()
------ ------ >do_timer(1); //jiffies_64变量加1
------ ------ ------ >update_times();
------ ------ ------ ------ >update_wall_time(); //更新墙上时间
------ ------ ------ ------ >calc_load(ticks); //更新系统的平均负载统计值
------ ------ >update_process_times();
------ ------ ------ >account_process_tick(); //更新进程时间
------ ------ ------ >run_local_timers(); //标记一个软中断去处理所有到期的定时器
------ ------ ------ ------ >raise_softirq(TIMER_SOFTIRQ); //执行TIMER_SOFTIRQ类型软中断
------ ------ ------ >scheduler_tick() //函数负责减少当前运行进程的时间片计数值,并且在需要时设置need_resched标志。在SMP机器上,还负责平衡每个处理器的运行队列。
18、内存管理
18.1、页分配(物理地址连续)
struct page //内存管理的基本单位,内核用struct page结构表示系统中的每个物理页
alloc_pages(); //分配2order个连续的物理页,返回struct page
page_address(); //把给定页转换成它的逻辑地址
__get_free_pages(); //分配2order个连续的物理页,返回第一个页的逻辑地址
get_zeroed_page(); //分配2order个连续的物理页并填充为0,返回第一个页的逻辑地址
__free_pages(); //释放页
18.2、字节分配
kmalloc(); //以字节为单位的分配函数,返回一个指向内存块的指针,分配连续的物理地址,常用GFP_ATOMIC或GFP_KERNEL类型标志
kfree(); //释放内存
vmalloc(); //分配的内存块的物理地址不一定连续,可以睡眠
vfree(); //释放内存,可以睡眠
18.3、slab
kmem_cache_create();//创建一个新的高速缓存
kmem_cache_alloc();//从缓存中分配对象
kmem_cache_free();//释放一个对象
kmem_cache_destory();//销毁一个高速缓存
18.4、高端内存
kmap(); //函数可以睡眠。高端内存或低端内存都可以使用。低端内存返回该页的虚拟地址;高端内存则会建立一个永久映射,再返回地址。
kunmap(); //取消永久映射
kmap_atomic(); //创建一个临时映射,不会睡眠
kunmap_atomic(); //取消临时映射
18.5、CPU独立数据
get_cpu(); //获取当前处理器,并禁止内核抢占
put_cpu(); //激活内核抢占
DEFINE_PER_CPU(type,name); //为系统每个处理器创建名字为name,类型为type的变量实例.
DECLARE_PER_CPU(type,name); //声明变量,防止警告
get_cpu_var(name); //返回当前CPU上的指定变量,禁止内核抢占
put_cpu_var(name); //重新激活内核抢占
per_cpu(name,cpu); //获取指定cpu上的变量值,不会禁止内核抢占,使用时要上锁
alloc_percpu(); //给系统每个处理器分配一个指定类型对象
free_percpu(); //释放CPU变量
19、VFS虚拟文件系统
super_block //超级块对象,代表一个具体的已经安装的文件系统;
inode //索引节点对象,代表一个文件(磁盘中);
dentry //目录项对象,代表一个目录项,路径的组成部分;
file //文件对象,代表进程打开的文件;
file_system_type //描述各种特定文件系统类型
vfsmount //描述一个安装文件系统的实例
files_struct //保存所有和单个进程相关的信息
fs_struct //包含文件系统和进程相关的信息
namespace //使每一个进程在系统中都看到唯一的安装文件系统
还有一些xx_operations的方法,看《笔记第十篇》.
20、块I/O层
struct buffer_head //缓冲区头
struct bio //正在现场活动的以片段链表形式组织的块I/O操作
struct bio_vec //描述了:片段所在物理页、块在物理页中的偏移位置、从给定偏移量开始的块长度。
还有各种I/O调度程序
21、进程地址空间
内存空间包含的主要内存对象:
代码段(text section)
数据段(data section)
bss段(block started by symbol) //主要是初始化为零的全局变量和静态变量
用户进程空间栈的零页的内存映射
任何内存映射文件;
任何共享内存段;
任何匿名的内存映射,比如malloc分配的内存
内存描述符
mm_struct //表示进程的地址空间
fork()
— |—子进程中
— --->copy_mm() //复制父进程的内存描述符
— --->allocate_mm() //子进程使用allocate_mm() 宏从mm_cachep slab缓存中分配得到mm_struct。
虚拟内存区域
struct vm_area_struct //指定地址空间内连续区间上的一个独立内存范围
struct vm_operations_struct //内存区域相关的操作函数表
操作内存区域
find_vma() //找寻一个给定的内存地址属于哪一个内存区域
find_vma_prev() //返回第一个小于addr的VMA
find_vma_intersection() //返回第一个和指定地址区间相交的VMA
创建地址区间
do_mmap() //函数创建一个新的线性地址区间。用户空间可以通过mmap() 系统调用获取内核函数do_mmap() 的功能。
do_munmap() //函数从特定的进程地址空间中删除指定地址区间。用户通过系统调用munmap() 从自身地址空间中删除指定地址区间。
22、页高速缓存
struct address_space //管理缓存项和页I/O操作,表示虚拟地址的vm_area_struct对应的物理地址的对等体。
struct address_space_operations //address_space结构的操作表
flusher线程 //用于页的写回
flusher_threads() //唤醒一个或多个flusher线程
bdi_wirteback_all() //将脏页写回磁盘
23、设备与模块
depmod //生成依赖关系
struct kobject //描述设备模型
struct kobj_type //描述一簇kobject所具有的的普遍特性
struct kset //kobject对象的集合,kset嵌入kobject中作为kobjcet组的基类
kobject_init() //初始化kobject
kobject_create() //创建和初始化kobject
kobject_get() //递增一个引用计数
kobject_put() //递减引用计数
struct kref //引用计数结构体
sysfs
kobject_add(); //将kobjcet导入到sysfs
kobject_del(); //从sysfs中删除一个kobject对应文件目录
struct attribute; //定义在kobj_type中,attribute属性负责将内核数据映射成sysfs中的文件
struct sysfs_ops; //描述了该如何使用默认的属性
sysfs_create_files(); //在默认属性上添加新文件属性的接口
sysfs_create_link(); //在默认属性上添加新符号链接属性的接口
sysfs_remove_files(); //删除一个file属性
sysfs_remove_link(); //删除link属性
内核事件层
kobject_uevent(); //内核代码中向用户空间发送信号
24、调试
oops //内核通过oops告知用户有错误的事件发生
ksymoops //对未解码的oops信息解码
BUG() 和BUG_ON() //提供了引发oops打印出错信息的能力
panic() //引发更严重的错误,不但会打印错误消息,而且还会挂起整个系统
dump_stack() //只在终端上打印寄存器上下文和函数的跟踪线索