Linux内核代码
全局描述符表GDT(Global Descriptor Table):
(1)在整个系统中,全局描述符表(注意这里是表,表只有一张)GDT只有一张(一个处理器对应一个GDT)。
(2)GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此积存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。GDTR中存放的是GDT在内存中的基地址和其表长界限。
每一个LDT自身作为一个段存在,它们的段描述符被放在GDT(全局描述符表中)中。
每一个段描述符由一下几部分组成:
(1)32位的base域,含有段的第一个字节的线性地址
(2)粒度位G,如果被清0,段大小以字节为单位,否则以4096(4kB)字节为基本单位
(3)20位的limit域,指定了以字节为单位的段长度,如果G被置0,则是一个非空段大小在1个字节到1M之间变化的,否则将在4KB到4GB之间变化
(4)系统标志S,如果被清0.则是一个系统段,存储内核数据结构,否则他是一个普通的代码段或数据段
(5)4位TYPE域,描述了段的类型特征和他存取权限,一下类型的段描述符被广泛采用
代码段描述符:
这个段描述符代表一个代码段,可以是GDT或LDT中,S标志位1
数据段描述符:
这个段描述符代表一个数据段,可以放在GDT或LDT 中,S标志为1,栈段一般通过一般数据段实现
任务状态段描述符:
这个段描述符代表一个任务状态段(Task state segment,TSS),也就是说这个段用于保存寄存器的内容,他只呢个放在GDT中,分组相应的进程是否正在CPU 上运行,其TYPE域的值分别为11或9,S标志为0
局部描述符表描述符:
这个段描述符代表一个LDT段,他只能放在GDT中,相应的TYPE为2,S标志为0
(6)2位DPL(描述符特权级),用来限制对这个段的存取,他表示方位这个段,cpu所需要的最小特权级
(7)segment-present 标志,设置为0 ,标志这个段当前不在主存中,Linux总是把这个域设为1,因此它从来不把整个段交换到磁盘上去。
(8)一个被称为D或B的额外标志,取决于是代码段还是数据段。D或B的含义在两种情况下稍微有区别。但是,段偏移量的地址是32位,他被置为1,如果偏移量是16位,它被置为0
(9)第53位是保留位,总是设为0
(10)AVL标志,可以被操作系统使用,但是被Linux
进程:
内核必须知道进程的优先级,进程的状态,给进程分配什么样的地址空间,允许访问哪些文件,这些信息都被记录在一个叫——进程描述,的结构中。
进程描述符号都是task_struct{}类型的。
进程状态:
TASK_RUNNING(可运行状态) :
进程要么在CPU中运行,要么准备运行.
TASK_INTERRUPTBLE(可中断的等待状态):
进程被挂起(睡眠),知道一些条件为真,这些条件包括,硬件中断,释放进程等待的资源,或传递一个信号,都可以唤醒一个进程,让进程进入TASK_RUNNING状态
TASK_UNINTERRUPTBLE(不可中断的等待):
该状态和前一个状态类似,但是不能被信号唤醒,要满足特殊的状态才能被唤醒。
TASK_STOPPED(暂停状态):
所谓的暂停状态是指,进程任然在使用CPU 但是,在某个时间,停止运行,当进程接收到信号SIGSTOP,SIGSTP.........等信号时,就会进入该状态。进程用于软件的调试
TASK_ZOMBIE(僵死状态):
进程的执行被终止,但是父进程还没有发布wait()类系统调用以返回死进程的相关信息,如果没有返回相应的信息,内核系统就不能 丢弃死进程中的描述符中的数据,因为符进程可能需要他。
进程描述符的存放:
task 数组中的仅仅包含进程描述符的指针,而不是进程描述符本身。因为进程是动态实体,所以被放在动态内存中,而不是放在永久性分配的内核内存区。Linux把两个不同的数据结构紧凑的放在一个单独为进程分配的存储区域内:(1)一个是内个态的进程堆栈(2)另外一个是紧挨进程描述符的小数据结构中——thread_info(线程描述符)
进程堆栈段和线程描述符总共有8KB的空间
注意:从效率上来讲,进程堆栈段和线程描述结构紧密结合带来的好处是:内核很容易从esp寄存器的值获得当前在CPU 正在运行的thread_info 结构的地址
进程链表:
为了对给定进程类型(可运行的所有进程)进行有效的搜索。内核建立几个进程链表(每个进程链表由指向进程描述符的指针组成,PID存放在进程描述符的PID描述符中)。进程描述符的数据结构中包含了一个指向下一个进程链表指针。
注意:进程链表的链表头是init_task描述符,他是所有进程的祖先。
相关函数:
宏:
SET_LINKS/REMOVE_LINKS //用来链表中插入和删除一个进程描述符
for_each_task()//用来扫描整个进程链表
进程链表——TASK_RUNNING状态的进程链表:
当内核需要寻找一个新的进程在CPU 上运行,必须考虑可运行的进程。如果扫描整个进程链表效率太低了。因此引入可运行状态进程双向链表,也叫做运行队列(runqueue),和一般的进程链表一样 ,init_task作为链表头,nr_running变量存放可运行队列进程总数
相关函数:
添加和删除:
add_to_runqueue()//把一个进程描述符插入到链表的开始
del_from_runqueue()//从进程中删除一个进程描述符
队列调度:
move_first_runqueue()
move_last_runqueue()
唤醒进程:
wake_up_process()//将一个进程的状态设置为TASK_RUNNING
进程链表——pidhash表及链接表:
当一个进程需要向另外一个进程发送信号,如果采用遍历扫描他消耗时间,然后就使用pidhash 算法。不懂!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
相关函数:
hash_pid()//在pidhash 表中插入进程
unhash_pid()//在pidhash 表中删除一个进程
find_task_by_pid()//查找散列表并返回给定PID 进程的进程描述符指针(如果没有找到则返回空指针NULL)
进程链表中——task空闲表象列表
每当进程被创建或撤销时,就要更新task数组。把一个新项有效的加到数组中去方式,不是线性的寻找列表中找到一个空闲项,而是内核维持了一个独立的包含空闲项的非循环双向列表,这个数组中的每一个空闲项指向下一个空闲项。而链表中的最后一个元素包含空指针,当一个进程被撤销时,task中对应的元素加到表头。
相关函数:
get_free_taskslot()//获得空闲进程描述符表项
add_free_taskslot()//添加空闲进程描述符表项
进程之间的亲属关系:
进程之间的关系无非是父子和兄弟之间的 关系,在进程描符中引出几个域来描述这种关系
等待队列:
运行队列链表把处于TASK_RUNNING 状态的进程组织到一起,同理运行队列列表也会把处于其他状态的组织到一起。
(1)TASK_STOPPED 或TASK_ZOMBIE状态的进程不链接在专门的链表中也没有必要分组,因为父进程可以通过进程PID ,或进程之间的亲属关系检索到子进程
(2)把TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE分类,每一类完成特定类型的,这样进程状态满足不了快速检索进程,因此有必要引入另外的进程链表——等待队列。
进程调度:
Linux内核代码之重要函数
modify_ldt(int func, void *ptr, unsigned bytecount):
该系统调用用来改变当前进程的局部段描述表
Linux内核代码之重要数据