Linux内核设计与实现——读书笔记(终)

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_SOFTIRQTASKLET_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()       //只在终端上打印寄存器上下文和函数的跟踪线索

《笔记第十五篇》
  
  
  

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr_zhangsq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值