分享
 
 
 

Linux内核设计与实现 读书笔记

王朝学院·作者佚名  2016-05-26
窄屏简体版  字體: |||超大  

linux内核设计与实现 读书笔记第三章 进程管理

1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的;所有进程的task_struct连在一起组成了一个双向链表3. 2.6内核的内核栈底放的是thread_info结构,其中有指向task_struct的指针;4. current宏可以找到当前进程的task_struct;X86是通过先找到thread_info结构,而PPC是有专门的寄存器存当前task_struct(R2)5. 内核栈大小一般是8KB6. 进程的五种状态:TASK_RUNNING, TASK_INTERRUPTABLE, TASK_UNINTERRUPTABLE(处于改状态的进程不能被kill,因为它可能正在等待很关键的数据,或者持有了信号量等), TASK_TRACED(被其他进程跟踪状态,具体状态表现不明), TASK_STOPPED(收到SIG_STOP信号,停止进程,相当于暂停进程,但是也可以恢复)7. 运行上下文分为“进程上下文”和“中断上下文”。系统调用时内核代表进程在进程上下文中执行代码,这时current宏是有效的,指向进程的task_struct,而且系统调用时内核使用的页表是用户态进程的页表;而在中断上下文内核不代表任何进程执行代码,而是执行一个中断处理程序,不会有进程去干预这些中断上下文,所以此时不存在进程上下文。8. 系统调用在陷入内核的瞬间应该是在中断上下文的,因为是软中断,只是陷入内核后又用了进程上下文9. 在每个进程的task_struct结构中,有一个parent指针指向其父进程,有一个链表表示其所有的子进程,用这样的结构构成了整个系统进程关系树。10. 内核的双向列表专用结构struct list_head11. 进程创建分为两个步骤:fork和exec,fork用来创建进程的结构,通过写时复制,父子进程共享进程空间(页表),父进程和子进程的区别仅仅是PID,PPID,某些资源,统计量(task_struct的结构); exec读出程序代码并执行之。通过写时复制,只有在需要写入进程地址空间时,才为子进程创建自己的进程地址空间。*** fork的开销其实就是复制父进程的页表 和 为子进程分配task_struct 结构fork的过程: fork() -> clone() -> do_fork() -> copy_PRocess()copy_process()的过程:a) 为子进程分配内核栈,创建thread_info结构,创建一份与父进程相同的task_struct结构b) 更改thread_info ,task_struct结构中的部分字段,将子进程与父进程区分开来c) 将子进程的状态设置为TASK_UNINTERRUPTIBLEd) 为子进程分配一个可用的PID(alloc_pid())e) 拷贝或共享父进程打开的文件,信号处理函数,进程地址空间等f) 设置子进程状态为RUNNINGg) 返回一个指向子进程的指针一般系统会优先唤醒子进程,因为如果优先唤醒父进程,父进程就有可能会写入,这样会触发写时复制,而子进程一般会调用exec12. vforkvfork保证父进程在创建子进程后被阻塞,除非子进程执行了exec,或者子进程退出vfork在fork有写时复制功能后的好处只有一个,就是:vfork不用拷贝子进程的页表13. 线程线程在linux内核中的实现就是一个进程,只是线程会与别的线程共享进程地址空间,共享信号等等创建一个有四个线程的进程,会有四个进程被创建(四个内核栈和四个task_struct),只要指明这些task_struct中共享同一个进程地址空间即可线程的创建和进程的创建的代码上的对比:线程创建: clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)进程创建: clone(SIG_CHILD, 0)从线程创建的代码也可以看出:线程创建时是共享父进程的 进程地址空间(CLONE_VM), 打开的文件(CLONE_FILES),文件系统信息(CLONE_FS), 信号处理函数(CLONE_SIGHAND)14. 内核线程内核进程需要在后台执行一些操作,所以需要创建一些内核线程(kernel thread)内核线程和普通线程的主要区别是: 内核线程没有独立的地址空间(task_struct中的mm指针是NULL),共享使用内核态的页表; 内核线程只在内核运行,从来不会调度到用户态;内核线程和普通线程的相同点有: 同样有状态,被调度,也可以被抢占有哪些内核线程:flush,ksoftirqd等, 用ps -ef可以查看,其中CMD栏是[]扩起来的都是内核线程创建方法: kthread_create(); wake_up_process()用来唤醒创建的线程; kthread_run()可以创建并使之运行; kthread_stop()停止内核线程15. 孤儿进程如果父进程比子进程先执行完,父进程要在线程组中找一个新的进程作为子进程的父进程,或者找init进程作为子进程的父进程16. 进程消亡进程消亡时是通过exit()来实现的,进行exit之后内核依然会保留其task_struct结构,直到其父进程调用wait()或waitpid()回收第四章 进程调度1. linux是抢占式多任务系统2. 通过调度程序选择一个进程来执行, 调度程序来决定什么时候挂起一个进程的运行,以便让其他进程得到允许机会,这种挂起操作叫做抢占。3. 进程在被抢占之前能获得运行的时间叫做进程的时间片。进程的时间片是固定的,预先设置好的。4. yield(), 进程可以通过该函数让渡被调度权5. 调度算法a) O(1)的调度程序O(1)调度程序对大服务器的工作负载下应用很理想,但是在交互式场景下不理想b) CFS完全公平调度算法改进了linux对交互式场景的不足6. IO消耗性进程和CPU消耗性进程linux调度程序通常更倾向于优先调度IO消耗性进程,但是也并未忽略CPU消耗性进程7. 进程优先级Linux采用了两种表示进程优先级的方法:a) nice值,nice值本来是Unix的标准做法。在linux中,nice值代表的是时间片的比例,nice值越大优先级越低,范围是-20到19b) 实时优先级,范围是0到99,越大优先级越高8. 时间片时间片过长,导致对IO消耗性进程的支持不好;时间片过短,进程调度就花去了更多的时间9. CFS调度算法实际上分给每个进程的是处理器占用比,这个占用比也会受到nice值得影响例子:假设系统只有两个进程,一个文本编辑程序(IO消耗性),一个视频编解码程序(CPU消耗性),系统初始时他们有同样的nice值,所以在启动后给它们分配的处理时间都是一样的,都是50%,因为文本编辑器消耗很少的CPU,所以它的CPU时间占比远小于本应该分配给他的50%,而视频程序就占用了超过50%的CPU时间,所以当文本编辑程序需要运行时,调度程序发现它的CPU时间比它应得的少很多,所以马上让他抢占运行;当文本编辑器运行完毕后,又进入等待,所以它消耗的CPU时间依然少,这样系统就能不断的马上响应文本编辑程序。CFS调度算法的主要思想是保证系统的公平使用,通过了这种方法可以自动的发现各个进程的CPU使用情况,根据这个使用情况动态的调整进程的调度和分配。CFS为每个进程被抢占前能运行的时间片的最小值是1ms。问题来了,一个运行了很久的IO消耗性进程和一个刚开始运行的CPU消耗性进程相比,可能会让IO消耗性进程被调度的可能性变慢,所以是不是说如果进程执行的时间过长了,要重启一下?10. Linux有多种调度器算法不同的进程被归入不同的调度器类中schedule()从最高优先级的调度器中选择一个最高优先级的进程来调度完全公平调度CFS是一种针对普通进程的调度器,linux中称为SCHED_NORMAL还有实时进程调度器11. Linux是何时运行调度器的?a)b) linux是通过need_resched这个标识来表明是否要进行执行一次调度的,哪些地方会设置这个标志:schedule_tick(), try_to_wake_up()等; need_resched标志保存在进程的thread_info里,这是因为访问current比访问全局变量更快c) 在返回用户空间或者中断返回的时候,内核也会检查need_resched标志,如果被设置,系统会在继续运行之前调用调度程序d) 抢占发生的时间:d.1) 用户抢占d.1.1) 从系统调用返回用户空间时d.1.2) 从中断服务程序返回用户空间时d.2) 内核抢占d.2.1) 从中断服务程序返回内核空间时d.2.2) 内核代码再一次具有可抢占性的时候:这里包含以下的含义: 只有进程没有持有锁就可以被抢占,如果持有了锁,系统是不可抢占的,在释放锁的时候且preempt_count减少到0的时候,说明当前可以被安全的抢占了,这时检查need_resched标志进行抢占。d.2.3) 内核显式调用schedule()d.2.4) 内核任务阻塞调度器入口:schedule()函数,作用是从最高优先级的调度器中选择一个最高优先级的进程来调度12,睡眠和唤醒当进程要等待时将自己的进程状态改成INTERRUPTIABLE或者UNINTERRUPTIABLE状态,并把自己从调度红黑树中移出到等待队列中,再调用schedule()调度下一个进程来运行睡眠时将进程挂到相应的等待队列上:DEFINE_WAIT(wait);add_wait_queue(q, &wait);while(!condition){ prepare_to_wait(&q, &wait, TASK_INTERRUPTIABLE); if(signal_pending(current)) ... schedule();}finish_wait(&q, &wait); //把自己移出等待队列唤醒wake_up()函数唤醒挂在等待队列上的所有进程,把这些进程的状态改成TASK_RUNNING,并把它加入到调度红黑树上,如果被唤醒的进程优先级比当前的优先级高,还要设置need_reschedule标志。唤醒要注意的是:可能存在虚假唤醒,可能是收到了信号唤醒了进程。所以在等待时要用一个while循环检查是否满足了条件,如果不满足可能是虚假唤醒,必须继续wait。13. 抢占和上下文切换上下文切换就是从一个进程切换到另一个进程去,用context_switch()函数完成,该函数在schedule()中被调用,该函数主要完成两个工作:switch_mm():把进程的虚拟地址空间切换switch_to():切换进程的处理器状态,保存、恢复栈信息和寄存器信息,还有其他任何与体系结构相关的状态信息14. 实时调度器两种实时调度策略:SCHED_FIFO和SCHED_RRSCHED_FIFO:先进先出,一直执行,直到自己释放CPU或者等待,无时间片概念SCHED_RR: 与SCHED_FIFO类似,但是有时间片概念,在耗尽预先分配给它的时间片之后就重新调度如何设置进程是实时进程?第五章 系统调用1. 系统调用是什么?为什么要引入系统调用?系统调用是用户进程和硬件设备之间的一个中间层引入系统调用有三个原因:a) 给用户提供一个统一的,抽象的接口和硬件设备打交道b) 通过系统调用这个中间层,防止用户异常操作硬件设备c) 虚拟化的思想,用户进程都是作为一个个单独的实体运行在虚拟空间中,在系统和用户进程之间提供这样的一层接口也是出于这个考虑,类似一个硬件上安装了多个虚拟机一样2. API, POSIX, C库POSIX是一套通用的API接口标准C库实现了POSIX规定的绝大部分API用户态调用流程:应用程序 -> C库 -> 系统调用Linux系统调用也是作为C库的一部分提供3. Unix接口设计的名言“提供机制而不是策略”—— 含义是:系统调用抽象了用于完成某种确定的目的的函数,至于这些函数怎么用完全不需要内核关心,是应用程序和C库来关心的。其实设计任何API都有这样的需求:只提供完成特定任务的接口,具体如何使用这个API是由使用者来关心的区别机制和策略会简化开发,机制是“需要提供什么功能”,策略是“怎样实现这些功能”。这样可以利用相同的API来适应不同的需求。4. syscall tablesys_call_table中保存了所有系统调用号的处理函数5. 中断陷入a) 通过软中断中断号是128int $0x80b) sysenter指令x86提供的新的进入系统调用的方法,更快更专业6. 系统调用的返回值和errno每个系统调用都会有返回值,返回值一般是long类型,为0表示成功,负数表示失败;返回值除了表示成功失败以外,根据系统调用的具体实现,可以返回功能结果,如getpid()系统调用就返回piderrno全局变量内保存的是错误号,可以通过perror()来获得错误描述。errno作为全局变量如何在多核上使用呢?7. 系统调用参数和返回值的传递系统调用时需要传递系统调用号和参数。系统调用号总是用eax传递,当参数个数小于5个时,用寄存器传递(ebx, ecx, edx, edi, esi), 当超过5个时,应该用一个单独的寄存器存放指向所有参数的用户空间地址的指针返回值是通过eax传递的8. 用户空间和内核空间的数据拷贝copy_to_user(dst, src, size);copy_from_user(dst, src, size);其实直接拷贝也是可以的。这两个函数主要是加了一些使用检查,对用户提供的指针进行检查,不让用户空间通过系统调用来操作内核空间的地址注意copy_to_user和copy_from_user都可能引起阻塞,当数据被交换到硬盘上时就会发生这种情况,此时,进程就会休眠,直到被唤醒后继续执行或者调用调度程序9. 系统调用要做很多检查工作,因为输入来自用户态,不能让用户态的错误操作导致内核态数据的错误capable()函数可以做一些权限检查10. 系统调用是可睡眠的和可抢占的可睡眠的保证了系统调用可以使用大部分的内核接口11. 函数可重入性系统调用要保证实现时可重入的,因为系统调用时允许被抢占的,所以当新的进程也调用该系统调用时保证可重入才不会出错。12. 不靠C库的支持,直接使用系统调用的方法例如:使用open系统调用#define NR_open 5_syscall3(long, open, const char *, filename, int, flags, int, mode)_syscall3是一个宏,它会设置好寄存器,并调用陷入指令。通过这个宏就创建了一个open()函数,返回值是long,有三个参数,这时就可以直接使用 long fd = open(filename, flags, mode); 调用系统调用了13. 最好不要新加系统调用,而是使用一些替代方案替代方案:a) 对于设备节点,可以使用ioctl自定义命令进行操作b) 对于信号量这种,其实也是文件描述符,所以也可以用ioctlc) 利用/proc或者/sysfs文件系统来和内核交互第六章 关键内核数据结构1. 链表,队列,映射,二叉树2. 链表经典的list_head循环双向链表struct list_head{ struct list_head *next; struct list_head *prev;};container_of宏,list_entry宏,可以通过这个宏方便的找到list_head所在的结构的首地址offset_of(type, member):获得member在type结构中的offset偏移,container_of中用到了这个宏#defineoffsetof(struct_t,member)((size_t)(char*)&((struct_t*)0)->member)#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) );})#define list_entry(ptr, type, member) / container_of(ptr, type, member)INIT_LIST_HEAD(struct list_head); // 初始化list_head链表头:LIST_HEAD()操作方法:list_add(new, head);list_add_tail(new, head);list_del(ptr);list_del_init(ptr); //删除并初始化该list_headlist_move(list, head); // 从一个链表中删除list,并加到head链表后面list_move_tail(list, head); // 从一个链表中删除list_empty(head); //判断是否为空遍历链表:struct fox{ int i; struct list_head *list;};struct list_head *head = ...;struct fox *f;struct list_head *p;list_for_each(p, head) { // 循环遍历链表 f = list_entry(p, struct fox, list);}另一个宏:list_for_each_entry(f, head, list); 可以实现和上面 list_for_each{ } 块一样的功能该宏的声明如下:list_for_each_entry(pos, head, member);还有一个反向遍历的:list_for_each_entry_reverse(pos, head, member);还有如果遍历的同时要删除的:list_for_each_entry_safe(pos, next, head, member); // 多了一个next的struct list_head指针,用来记录nextlist_for_each_entry_safe_reverse(pos, next, head, member);如果要并发操作链表,必须使用锁。3. 队列FIFO。生产者消费者模型kfifo是内核的通用实现。创建队列:struct kfifo fifo;int ret;int size = PAGE_SIZE;ret = kfifo_alloc(&fifo, size, GFP_KERNEL); //size必须是2的幂char buffer[PAGE_SIZE];kfifo_init(&fifo, &buffer, size);入队列:unsigned int kfifo_in(struct kfifo *fifo, const void *from, unsigned int len); // 拷贝from指向的len大小的数据到fifo中出队列:unsigned int kfifo_out(struct kfifo *fifo, void * to, unsigned int len); //拷贝出长度为len大小的数据到to中其他操作:见书本4. 映射与std::map类似,有以下操作:Add, Remove, Findlinux实现了一个专用的类似map的结构:是一个唯一的UID到一个指针的映射5. 二叉树rbtree (看一下红黑树的原理)查找操作多的情况可以使用,如果查找的少,不如用链表第七章 中断和中断处理1. 中断上下文又称原始上下文,该上下文中不可阻塞2. 内核在收到中断之后要设置设备的寄存器 关闭中断,设备的配置空间一般有中断位,采用level中断方式,必须把这个中断位设置3. 上半部与下半部例如,网卡,上半部在收到中断进入中断上下文后要设置硬件寄存器,同时把数据快速的拷贝到内核空间4. request_irq注册中断IRQF_SHARED标志:共享中断线IRQF_DISABLED标志:处理中断时关闭其他中断free_irq()释放中断5. 中断上下文与进程无关,不能睡眠中断上下文的栈:有两种:每个cpu单独的中断栈;或者使用被中断进程的内核栈一般进程的内核栈是两页,在32位机器上就是8KB,在64位的机器上就是16KB6. /proc/interrupts7. 中断控制禁止中断 : local_irq_disable() local_irq_enable();这两个函数有缺陷:不能嵌套调用,所以有下面两个函数:local_irq_save(flags) local_irq_restore(flags) 它们会保存中断状态(就是之前是被禁止还是被启用的状态)这些函数是当作临界区锁来用的,当软中断上下文和中断上下文有共享数据时,就要用这些函数来充当锁local_irq_disable()禁止了所有的中断禁止指定irq上的中断:disable_irq(irq) disable_irq_nosync(irq) enable_irq(irq) synchronized_irq(irq)这些函数可以嵌套,所以调用多少次disable就要调用多少次enable共享中断线的irq不能被禁止,所以这些API主要在老式的设备上采用,PCIe设备强制要求共享中断线8. 判断当前是否在中断上下文in_interrupt() : 内核正在执行中断处理程序或者下半部时返回非0in_irq() : 内核正在执行中断处理程序时返回非09. 中断处理程序只能在一个CPU上运行第八章 下半部1. 下半部是什么下半部是比中断处理程序稍缓的任务,可以在中断处理程序处理完最紧急的任务之后处理的任务2. 下半部可以有多种实现方法上半部只能用中断处理程序实现,而下半部可以用下列方法实现:软中断,tasklet ,工作队列3. 软中断系统最多能注册32个软中断,目前内核总共用的软中断有9个一个软中断不能抢占另一个软中断。唯一可以抢占软中断的是中断处理程序。不过,其他的软中断,即使是相同类型的软中断,也可以在别的处理器同时运行。通常,中断处理程序会在返回前标记它的软中断,使其在稍后执行。这个步骤叫触发软中断。那么,何时会执行待处理的软中断呢?a) 在中断返回时b) 在ksoftirqd内核线程中c) 在显示的检查和执行待处理的软中断的代码中,如网络子系统中do_softirq()是唤醒软中断的函数,其简化代码如下:目前只有两个子系统直接使用软中断:网络子系统和SCSI子系统; tasklet也是用软中断实现的注册软中断处理程序:open_softirq(softirq_no, softirq_handler);raise_softirq(softirq_no);软中断不能睡眠,能被中断处理程序抢占。如果同一个软中断在它被执行的同时再次被触发了,那么在其他CPU上可以同时执行其处理程序,这意味着要在软中断的上下文要采用锁来保护,但是如果加锁的话,使用软中断的意义就不大了。所以软中断中一般使用的是单处理器数据(仅属于某一个处理器的数据)。所以软中断作为BH用的比较少,一般采用tasklet。4. tasklettasklet也是用软中断实现的,有两个软中断和tasklet有关:HI_SOFTIRQ, TASKLET_SOFTIRQtasklet有两个单处理器数据结构: tasklet_vec 和 tasklet_hi_vectasklet 可以保证同一时间里给定类别中只有一个tasklet会被执行,不同类别的tasklet可以同时执行,所以使用tasklet可以不用过多的考虑锁的问题tasklet的使用:声明:(name是tasklet的类别)DECLARE_TASKLET(name, func, data);DECLARE_TASKLET_DISABLED(name, funct, data); //声明默认disable的tasklettasklet处理程序的格式:void tasklet_handler(unsigned long data)调度自己定义的tasklettasklet_schedule(&my_tasklet);启用和禁用tasklettasklet_disable(&my_tasklet); // 如果指定的tasklet正在执行,则等到执行结束再返回tasklet_enable(&my_tasklet);一个tasklet总在调度它的CPU上执行,这是希望能更好的利用处理器的高速缓存一个tasklet只要在一个CPU上被执行了,就不会同时在另一个CPU上执行它,(因为有可能一个tasklet在执行的时候,中断处理程序在另一个CPU上又激活了这个tasklet,这样在另一个CPU上如果发现这个tasklet正在被执行,便不会再执行了)5. 软中断调度时机和ksoftirqd/n内核线程由于软中断可以被自行重新触发,所以如果软中断中不断触发软中断,而且软中断立即被检查执行,那么就会导致系统的CPU被软中断占用的过多。另一个方案是:自行重新触发的软中断并不马上被检查执行,而是在下一次中断处理程序返回后检查执行。ksoftirqd/n是在每个cpu上都有一个的内核线程,只要有未处理的软中断,在空闲CPU上就会被调度执行。6. 工作队列工作队列把工作交给一个内核线程去执行,总是在进程上下文中执行。而软中断和tasklet可能在中断上下文执行,也可能在进程上下文执行。因为总是在进程上下文执行,所以工作队列可以重新调度甚至睡眠。如果下半部中要允许重新调度,那么可以使用工作队列。工作者线程: events/nworker_thread()是核心函数:下面是run_workqueue()workqueue_struct, cpu_workqueue_struct, 以及work_struct之间的关系工作队列的使用:DECLARE_WORK(name, void (*func) (void *), void *data); //静态创建INIT_WORK(struct work_struct *work, void (*func) (void *), void *data); //动态创建工作队列处理函数void work_handler(void * data);调度工作执行schedule_work(&work);schedule_delayed_work(&work, delay);问题:每个CPU上都有一个events/n内核线程,那么schedule_work(&work)时是把work挂到哪个CPU上的events list上的呢?7. 禁止下半部local_bh_disable()local_bh_enable()这两个函数可以嵌套,即disable几次就需要enable几次;这是因为这两个函数使用了preempt_count(抢占计数)来记录引用计数第九章 内核同步1. 造成并发的原因a) 中断b) 软中断和taskletc) 内核抢占:内核任务别抢占d) 睡眠以及与用户空间的同步:内核任务被用户进程抢占e) SMP CPU:多处理器并行执行用户态进程一般只需要考虑两个因素:SMP CPU 和 进程抢占SMP CPU可能会并行执行进程抢占需要代码支持可重入这两种情况都主要是怕两个进程(线程)同时访问全局数据或共享数据,如果两个进程(线程)不共享数据那么就肯定是安全代码,如果要共享数据,就必须加锁2. 中断安全代码,SMP安全代码,抢占安全代码(即可重入代码)3. 只要是共享数据,就要加锁;所以尽量不要共享数据;(个人想法:在要进入IO消耗性代码时,可以考虑共享数据,例如:在要访问数据库时,因为访问数据库时肯定要有IO操作,多个线程要访问同一个数据库连接时要加锁,因为反正进程要切换,而且用一个数据库连接还可以做缓存。如果采用多个数据库连接,那么缓存也变成了问题)4. 记住:给数据加锁,不要给代码加锁5.6. 死锁死锁的原因:多个进程,多个资源;多个进程分别持有了一部分资源,又要请求其它资源;结果互相等待。7. 锁的争用和加锁粒度高度争用的锁容易造成系统的瓶颈加锁粒度:细粒度的锁可以降低锁的争用第十章 内核同步方法1. atomic_t 原子操作2. 自旋锁DEFINE_SPINLOCK(mr_lock); // 静态初始化spin_lock_init(&mr_lock); //动态初始化spin_lock(&mr_lock);...spin_unlock(&mr_lock);注意:在中断上下文要用spin_lock_irqsave(&mr_lock, flags);...spin_unlock_irqrestore(&mr_lock, flags);如果能确定中断在加锁之前是激活的,那么可以用下面的APIspin_lock_irq(&mr_lock);...spin_unlock_irq(&mr_lock);非阻塞操作spin_try_lock(&mr_lock);spin_is_locked(&mr_lock);自旋锁和下半部,在下半部中要用下面的API:spin_lock_bh()spin_unlock_bh()注意:如果下半部和进程上下文共享数据,就要用这个来加锁如果下半部和中断上下文共享数据,就要用spin_lock_irqsave/spin_unlock_irqrestore读写自旋锁DEFINE_RWLOCK(lock)read_lock/read_unlockwrite_lock/write_unlock3. 信号量(semaphore)DECLARE_MUTEX(name)sema_init(sem, count)init_MUTEX(sem)init_MUTEX_LOCKED(sem)获取信号量:down_interruptible() 如果无法获得,该函数把进程状态设为TASK_INTERRUPTIABLE,并进入睡眠down_trylock() 非阻塞获得semdown() 无法获得时进入不可中断睡眠(TASK_UNINTERRUPTIABLE)up() 唤醒读写信号量4. 互斥体(mutex)计数是1的信号量DEFINE_MUTEX(mutex)mutex_init(&mutex)mutex_lock(&mutex)mutex_unlock(&mutex)比较:信号量和互斥体:一般应用中互斥体,除非互斥体无法满足需求信号量和自旋锁:中断上下文只能用自旋锁5. 完成变量思想和信号量类似,只是一种针对更简单问题的一种解决方案init_completion(struct completion *);wait_for_completion()complete()6. 大内核锁lock_kernel()unlock_kernel()尽量少用7. 顺序锁特定情况下使用,一般是写很少,读很多的时候,而且数据操作简单时较好8. 禁止抢占preempt_disable()preempt_enable()preempt_enable_no_resched()preempt_count()9. 内存屏障由于编译器会进行读写重排序,所以加入内存屏障来确保读写顺序在SMP上常见,多个CPU操作同一个数据时,可能一个CPU似乎已经写入了,但是另一个CPU读出的还是原来的值,常常出现在多线程共享数据而又没有加锁的情况下rmb() 确保跨越rmb()的读操作不会被重排序wmb() 确保跨越wmb()的写操作不会被重排序mb() 确保跨越mb()的读写操作都不会被重排序smp_rmb() / smp_wmb() / smp_mb()第12章 内存管理1. 整页的分配和释放2. kmalloc和slabkmalloc是基于slab分配器实现的。slab分配器主要解决的是分配不规则字节内存造成的内存碎片的,还有加快内存的分配和释放时间kmem_cache_create()3. vmallocvmalloc和kmalloc类似,都是分配物理内存,但是kmalloc分配的物理内存和虚拟内存都一定是连续的;而vmalloc分配的虚拟内存是连续的,物理内存可能不连续另外,kmalloc的性能更好,因为vmalloc必须建立专门的页表项,而kmalloc因为是直接映射的,所以无需页表(TLB:缓存虚拟地址到物理地址的高速缓存)所以vmalloc在内核中一般只是在要分配大块内存时使用,例如加载模块时vfree(void *)是否vmalloc分配的内存4. 栈上的内存管理32位和64位体系结构的页面大小分别是4KB和8KB,一般内核进程的栈是2页,所以一般是8KB和16KB千万不要在内核栈上面分配大量数据,容易造成栈溢出5. 高端内存的映射使用alloc_pages()分配的高端内存返回的是page*结构,因为高端内存没有直接的逻辑地址映射,所以要建立页表来映射永久映射: kmap/kunmap (可能睡眠)临时映射: (在不能睡眠的情况下使用) kmap_atomic/kunmap_atomic (其实是有一组保留的映射地址)6.分配函数的选择一般用kmalloc和_get_free_pages如果要从高端内存分配,用alloc_pages + kmap如果分配的内存较大,而且无需物理上连续,就用vmalloc如果要做分配管理,请用slab分配器第十三章 虚拟文件系统1. VFS文件系统抽象层2. Unix的四种和文件系统相关的传统抽象概念文件,目录项,索引节点(inode,存储文件的元数据),mount节点另外,文件系统的控制信息被放在了超级块中===摘抄===大部分UNIX文件系统种类具有类似的通用结构,即使细节有些变化。其中心概念是超级块superblock, i节点inode, 数据块data block,目录块directory block, 。其中超级块中包含了关于该硬盘或分区上的文件系统的整体信息,如文件系统的大小(其准确信息依赖文件系统)等。 i节点包括除了名字外的一个文件的所有信息,名字与i节点数目一起存在目录中,目录条目包括文件名和文件的i节点数目。 i节点包括几个数据块的数目,用于存储文件的数据。 i节点中只有少量数据块数的空间,如果需要更多,会动态分配指向数据块的指针空间。这些动态分配的块是间接块;为了找到数据块,这名字指出它必须先找到间接块的号码。

=========3. 超级块超级块记录文件系统基本信息超级块的操作主要是CRUD inode4. inodeinode存放的是文件或目录的所有信息:几个时间,引用计数,uid,gid,文件大小,权限一个inode就代表了一个文件:可以是普通文件,也可以是管道,块设备,字符设备等inode的操作:CRUD文件,修改权限,truncate,mkdir, mknod, rename等等通过inode可以寻找到dentry对象5. dentry目录项对象目录和普通文件都是一个目录项对象主要用来解析文件路径目录项结构中维护了整个文件目录树dentry的操作:判断目录对象是否有效,比较文件名,查找文件等6. 文件对象文件对象存放的是文件信息:文件路径,文件操作file_Operations, 文件offset,页高速缓存地址文件的操作: read/write/lseek/ioctl/fsync/open/close/mmap...

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有