Linux内核设计与实现(原书第3版)
- p28 do_fork():有意让child先执行,一般child马上会exec(),避免了CoW的额外开销 ...?
- 线程创建/fork/vfork 3者调用clone()时参数的不同
- wait4
- O(1)调度:根据nice分配时间片
- 调度器类?CFS是normal情况下的?
- CFS:优化交互任务的实时响应,挑选vruntime最小的那个,rbtree中的leftmost
- p47 规划化:
- if(!sleep) se->vruntime -= cfs_rq->min_vruntime;
- 休眠进程:位于某个wait_queue上(等待队列使用:inotify_read(),这里是异步IO吗?)
- 可中断的:如果收到信号,会提前醒过来
- 用户抢占:内核返回user space时,如果need_sched,会导致schedule()调用
- 内核抢占:只要没有持有锁,正在执行的代码就是可重入的,也是可抢占的(安全性指的是不会崩溃?但正确性需要用户自己保证?)
- thread_info#preempt_count 加锁的次数
- if( need_sched && !preempt_count) schedule();
- 如果代码自行调用schedule(),它应该保证该抢占调度是安全的
- 2种实时调度:SCHED_FIFO(一直执行,除非让出CPU)、SCHED_RR(一直执行,直到时间片用光,带deadline的执行?)
- 处理器绑定:task_struct#cpus_allowed sched_setaffinity()
- syscall
- *为什么getpid()返回的是tgid
- 软中断:int $0x80,或sysenter(更快地陷入)
- 权限检查:suser() --> capable()
- 用户空间访问系统调用(C库的实现方式):_syscalln() n为0~6,代表需要传递的参数个数
- 确保你需要syscall,否则使用sysfs更好
- 内核数据结构
- list_head:要遍历的同时删除,list_for_each_entry_safe(p, next, header, member_name)
- 队列 kfifo
- idr:映射用户空间UID
- rbtree
- 中断
- 注册:request_irq(irq_number, handler, flags*, name, dev) 由于它最终会调用kmalloc(),可能会睡眠!
- 无须可重入
- handler:使用spin_lock以避免SMP上其他CPU同时访问
- ret_from_intr()
- /proc/interrupts
- local_irq_disable();之前,先local_irq_save(&flags);安全一点,flags不能传递给另一函数,必须是同一stack frame
- 状态判断:in_interrupt()(中断处理)、in_irq()(下半部)
- 下半部
- 最早的叫bottom half(BH)
- 任务队列(task queue):不够灵活,对于性能要求高的网络子系统,不能胜任
- 2.3+ 软中断与tasklet:2个不同类型的tasklet可同时执行
- 2.5+ 彻底丢弃BH、任务队列 --> 工作队列
- 软中断:softirq_action、softirq_vec[NR_SOFTIRQ=32](实际使用9个,映射为优先级?)
- tasklets:HI_SOFTIRQ TASKLET_SOFTIRQ(内部用动态的链表组织)
- ksoftirqd/n
- work queue:退后到某个内核线程执行(!oi see)
- workqueue_struct 工作线程worker_thread()
- cpuworkqueue_struct 每CPU的
- work_struct
- 老的任务队列及keventd
- 内核同步
- atomic_t
- spin lock
- 如果确定中断在加锁前是激活的,则无须保存irq状态:spin_lock_irq(&lk);//这个函数名其实挺费解的~~
- spin_lock_bh():加锁,同时禁止所有下半部的执行
- 读写自旋锁(rwlock):递归地获得同一个读锁是安全的
- 信号量
- 不一致的命名方法:init_MUTEX?
- down_interruptable/up(&mr_sem);
- rw_semaphore
- 互斥体(mutex)指的是任何可以睡眠的强制互斥锁,比如计数为1的信号量
- 完成变量(completion)
- 历史遗留话题:BLK
- 2.6+ 顺序锁(seqlock_t):类似于STM?,seq锁对写更优先
- 抢占:preempt_disable(); 支持嵌套
- barrier:rmb(); wmb(); read_barrier_depends();*(确保屏障前的读在屏障后的读之前完成)barrier()(编译器屏障)
- 定时器和时间管理
- HZ:x86默认为100(10ms单位),其他体系结构为250或1000
- jiffies
- time_before宏:把unsigned long转换为long再相减!
- struct timespec xtime; //精确到ns?
- timer_list
- 延迟执行:e.g.重新设置网卡的以太模式需要2ms
- udelay ndelay mdelay BogoMIPS
- schedule_timeout()
- mm
- struct page: 在于描述物理内存本身,而不是包含在里面的数据
- struct zone:x86-32上normal区域为16~896MB
- kmalloc()
- gfp_mask
- vmalloc()
- slab层
- 每个高速缓存:p = kmem_cache("task_struct", sizeof(struct task_struct), ARCH_MIN_TASKALIGN, SLAB_PANIC|SLAB_NOTRACK, NULL);
- 内核栈:1或2页,如果1页,则使用单独的中断栈
- 高端内存映射:__GFP_HIGHMEM获得的页没有逻辑地址?
- 永久映射:kmap 有数量限制
- 临时映射:kmap_atomic
- 老式的每CPU的分配:data[NR_CPUS] --> get_cpu()禁止内核抢占
- 新的每CPU:DEFINE_PER_CPU(type,name); --> get_cpu_var(name)
- VFS
- 操作对象:{super, inode, dentry, file}_operations
- inode
- permission(inode,mask):允许特定的访问模式,返回0,支持访问控制链(ACLS)的文件系统需要
- setxattr:允许key/value与文件关联(hiden/invisible?)
- struct file:没有对应的磁盘数据,通过f_dentry -> 指向相关的inode -> 文件是否dirty
- 和文件系统相关的数据结构
- struct file_system_type;
- struct vfsmount;
- 和进程相关数据结构
- struct files_struct;
- 块I/O层
- struct buffer_head; BH_xxx标志,弊端:大块数据的IO分解为多个buffer_head,不必要的负担和空间浪费
- struct bio; 代表的是I/O操作,而buffer_head仅描述磁盘的一个块
- struct bio_vec: { page, offset, len } 三元组
- 请求队列(request_queue)
- I/O调度
- Linus电梯:尝试合并读请求;防止饥饿;根据扇区位置调整插入(优化物理访问性能?);默认到队列尾部
- 最终期限:写是异步的,而读必然是同步阻塞的!--> 减少读请求饥饿现象(降低了全局吞吐量);3个队列:读、写、派发
- 预测I/O(默认调度):请求提交后有意空闲片刻,如果这期间应用有相邻的其他读请求,则可提高性能(总感觉这里的设计考虑因素并不多??)
- CFQ:I/O请求按进程放入不同队列
- Noop:仅执行合并,用于无寻道开销的,如闪存卡
- 进程地址空间
- mm_struct
- vm_area_struct
- VMA标志,vm_operations_struct
- 可通过内存描述符的mmap和mm_rb域访问内存区域(分别使用list和rbtree)
- 页表:PGD、PMD、PTE
- 页高速缓存和回写
- struct address_space; //physical pages of a file ...
- radix_tree
- flusher线程、pdflush
- struct address_space; //physical pages of a file ...
- 设备与模块
- 字符设备(只提供流式访问)、块设备(可随机读写)、网络设备
- kobject ktype kset
- sysfs:把kobject与dentry联系起来
- kobject_uevent():内核空间向用户空间发送信号
- 调试
- 可移植性
- 字节顺序:u32 __cpu_to_be32(u32) 把cpu字节顺序转换为高位优先