前沿
1 内核中的内存都不分页,所以要珍惜每一个字节。同时可以想到,内核的栈是固定大小的。不能动态分配(用户进程的栈是可以控制和动态增长的,因为无论如何他都是虚拟内存)
2 linux内核必须使用自旋锁或者信号量来保护临界资源,因为linux内核支持多处理器/中断是异步到来的/linux内核是可抢占的。
3 内核是无法使用C库的,因为C库本身就是依赖于内核而实现的。比如printf内部调用的是wirte写屏幕。
一 进程
1 内核中有一个双向循环链表的进程描述符队列,每个描述符表示一个进程的所有信息。每个进程的描述符中都有父进程和所有子进程都是有双向链表的表示。
2 每个进程都有一个用户堆栈和一个系统堆栈,其中用户堆栈位于线性地址;内核堆栈位于内核中,保存进程切换时的寄存器等信息,用户不可操作。进程系统堆栈中有一个结构体的指针指向了内核中该进程对应的进程描述符。
3 创建进程的过程
首先为新进程创建一个内核栈和相应的进程描述符,内容和父进相同。
然后修改与父进程描述符不一样的内容,例如PID等,接着设置子进程装备标识。
根据传入的参数,拷贝或者共享父进程已经打开的文件/信号处理函数。
调度子进程开始运行,这时一般子进程会调用exec加载代码段。
父进程运行,如果子进程没有加载新的代码段,就把自己的代码段加载到子进程。
4 linux内核不区分线程,线程只不过是共享某些资源的进程而已。
5 进程退出的时候需要将资源清理并通知父进程,如果存在子进程则需要将子进程过继给init进程或者要销毁进程的父进程。
【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)
内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
二 调度
1 进程过多的在IO等待,则他的优先级会被内核减小(即优先级升高)。反之,如果内核调度程序已分配CPU时间给进程即全部用完,则内核会将该进程优先级升高。
2 内核会给所有正在运行态的进程分配一个时间片,优先级越高的进程或者的运行时间越长。当给每个进程分配完时间片之后,内核调度器就会轮转的运行每个进程,当某个进程的时间片用完之后就不会在或者执行,当所有进程的时间片都用完之后。调度程序会重新给所有运行的进程分配时间片。再重复上面的过程。 可以注意到,调度器给进程A分配了100ms,但是实际的运行是A分5次,每次20ms运行完毕。
3 每个处理器有一个任务队列包含所有等待执行的进程。
4 对临界资源上锁时,如果临界资源有一定的上锁顺序,那么是可以避免死锁的。
5 每个CPU对应一个结构体,如下
Strucy prio_array{ //活动优先级数组
Int nr_active; 任务数目
U_long bitmap[5]; 为了提高效率,例如优先级为7的进程处于run状态,则第七位置1,方便调度程序知道哪些优先级里面有进程可以被调度
Struct list_head queue[140]; 最多140个优先级,数组中每个元素代表了该优先级下的一个进程链表。
6 重新计算时间片 调度程序为每个CPU分配了2个优先级数组,活动的和备份的。活动的优先级数组中的时间片用玩的进程会被放入到备份优先级数组中,当活动优先级数组中的进程时间片全部为0的时候,就会进行活动和备份的相互替换。这样可以在O(1)的时间,完成重新计算时间片。非常强大。
7 调度程序的运行过程 schedule()
首先在活动优先级数组中的bitmap中找到第一个被置位的位,该为对于的优先级最高的可执行进程。
然后,调度程序选择这个优先级链表里面的第一个进程,然后调用context_switch()进行进程切换,从当前正在执行的进程切换到优先级最高的进程,并执行这个目前优先级最高的进程。
8 如果一个进程需要等待IO/或者键盘输入等,则该进程会被内核放入等待进程队列。
9 如果是多处理器,那么每个处理器会有一个进程队列,这就会有负载均衡的问题。Linux内核是用load_banlance()来处理负载均衡的,这个函数内部无非就是首先找到负载最大的一个CPU,然后从他的备份优先级数组中取出优先级高的一个进程,将他复制到负载小的CPU的进程队列中。如果还是需要,则重复上面过程,直到小于25%的差。
10 抢占分为用户抢占和内核抢占。用户抢占一般发生在从系统空间(或者终端)返回用户空间。内核抢占
11 另外linux也也提供了实时调度算法,很简单,就是有实时任务队列有进程时,必须让他们优先执行完毕或者阻塞。
12 linux系统调用的大概过程是:
1)用户进程调用API
2)API调用系统函数
3)参数和系统函数号写入相应的几个寄存器
4)int80触发软中断,根据寄存器参数来执行相应的系统调用
5)调用完毕 ,按上面相反的过程返回
三 中断相关
1 中断的产生过程:
1) 键盘等触发中断。
2) 中断信号传送到中断控制器。
3) 中断控制器吧中断信号传送到处理器
4) 处理器中断当前处理的程序转入处理中断
5) 处理器通知操作系统
6) OS调用中断处理程序,并在接收完全之后返回OK给产生中断的硬件(具体可参看中断向量表里对于的函数)
7) OS中断处理程序处理接受到的中断内容,进入中断的下半部分处理。
2 中断处理程序打断了其他程序,并且他本身是不可再打断的,所以中断处理程序必须简洁。中断处理程序共享被中断进程的内核栈
3中断的下半部分处理 在linux2.6内核里面,中断的下半部分处理有3种方式。
1) 软中断方式 软中断是在内核中建立一个32个元素数组soft_inter[32],数组中每个元素包含参数和函数入口地址。当中断处理程序接收到硬件中断并从硬件中断确认和取出数据后,就会像soft_inter[32]中某个元素注册,之后某个时间,do_softinter()会执行,它会遍历数组找出可以执行的并依次全部执行。这样就能完成中断的后半部分处理。即完成整个中断处理过程。
2) Tasklets 软中断一般使用在对实时性要求很高的场合。例如网络等,一般情况下都用tasklets
3) 工作队列
4 执行软中断的进程是内核进程
5 一般硬件的都实现了对整数和位的原子操作
6 同步方法:
1)自旋锁 线程访问已经被使用的自旋锁时会进行循环等待(不进入休眠或者进程调度),非常耗费CPU,所以自旋锁一般用于时间很短的互斥操作。另外注意,中断处理程序中只能使用自旋锁,不能使用信号量,因为中断处理程序不是单独的线程,使用信号量会导致休眠,而无法回到中断处理程序。
7 提高时钟节拍(系统频率)也有不好的影响。系统频率越高,系统负担越大,但是越准确。
8 一个好的延时代码是(此段代码不能运行在中断处理程序中):
While(time_before(jiffies,delay))
Con_resched();
当判断延时的时候,允许进程调度程序来调度更重要的进程运行。注意jiffies是系统时间,每一个时钟节拍在时钟中断处理程序中更新加1。必须为volatile类型。
Jiffies是时钟中断的频率,和CPU的频率有所区别,CPU的频率可以让延时函数达到毫秒,微妙级。时钟中断的频率只能让延时达到秒级。
内核中内存管理
1 对于一个512M的内存,内核中对应了512M/4K个元素组成的页链表。链表中每个元素代表了一个4K的物理地址。当系统需要使用内存时,内核就会在链表中找一个没有使用的节点。
2 但是由于物理内存不同区域可能有不同的限制,所以Linux将物理内存划分为了3个区。
1) 可以直接执行DMA指令的区(一般为小于16M的物理地址空间)
2) 普通可直接映射到页的区域
3) 高端内存,可能高过了最大的虚拟内存
3 分配内存的时候,必须保证在虚拟地址是连续的,物理地址是否连续要看需求。
4 内核中slab层:
1)频繁使用的数据应该缓存他们。类似于线程池,内存池。
2) 让部分专属于某个处理器。这样就不用加锁。
3) 不同用途的缓存分开存放
虚拟文件系统VFS
1 linux中文件内容和文件信息(索引节点)是分开存放的。一般来说ext文件系统的硬盘上有单独存放索引焦点的超级快和单独的扇区。但是对于fat,ntfs等非linux文件系统来说,就必须在内存中虚拟一个这样的索引节点区。
2 VFS是对不同文件系统的抽象。其中包含几个重要结构
超级块(存储文件系统信息)
索引节点(文件和目录信息)
目录项对象(每个文件和目录都属于目录项,目录项指示了文件的包含个查找关系,不是保持在磁盘上,说的简单点目录项里面缓存的就是目录的结构信息 例如 /src/linux2.6/foo.c ,而 索引节点里面保存的是 src linux2.6 foof.c 的信息)
这些结构都是用C来模拟C++的类。
文件对象(进程已经打开的文件在内存中的表示)