PS:这次仅是linux内核课程总结,用于本人自身备考所用。
一、Linux导论
Linux是一个类Unix操作系统。具有现代完整的Unix中所期望的所有功能,包括真正的多任务、虚拟显存、共享库、按需加载、共享的写时拷贝可执行文件、适当的显存管理以及包括IPv4和IPv6的多堆栈网路。
Linux属于GPL许可证。
由于Linux最初是由黑客开发的,所以此处稍为加一点黑客的价值观:
使用计算机以及所有有助于了解这个世界本质的事物都不应遭到任何限制。任何事情都应当亲手尝试。
2.信息应当全部免费。
3.不信任权威,倡导去中心化
4.判定一名黑客的水平应当看他的技术能力,而不是看他的学历、年龄或地位等其他标准。
5.你可以用计算机创造美和艺术
6.计算机使⽣生活更更美好
二、Linux内核基础知识
1、寄存器
1)每条指令读取后eip自增
2)指令的厚度可能不同
3)通过call,ret,jmp等指令可以更改eip的值
此处只简单介绍最主要的几个寄存器。
esp:堆栈顶表针,寄存器中储存栈顶地址。
ebp:堆栈基表针,寄存器中储存栈底地址。
eip:指令表针,寄存器中储存着下一条指令的地址。
eax:累加器。
CS:代码段寄存器。
DS:数据段寄存器。
SS:堆栈段寄存器。
2、内存&显存指令
显存指令有MOV,PUSH,POP等。
分别对应:寄存器轮询、立即轮询、直接轮询、间接轮询、变址轮询。
分别对应push、pop、call、ret指令的操作。
此处补充下enter和leave。
此处非常解释下栈和寄存器上面内容的关系,用汇编代码表示栈的时侯常常弄错了。这儿举3个反例。
1)第一个案例。
构建一个空栈(PS:求推荐画栈堆的软件,word作图太累了,这儿就演示第一个反例,然后直接展示最后的栈堆情况。)
pushl$8($表示立刻数,此处的意思为把$8压栈)
movl%esp,%ebp(把%esp的值赋给%ebp,该指令常用语堆栈对齐)
subl$4,%esp(%esp上面的值减$4)
movl$8,(%esp)(此处把$8联通到%esp中的内容为地址,对应的栈中)
此处第一个案例就结束了。最终状态如上图所示。
2)第二个案例
此案例跟第一个案例得到的最终状态是一样的,由于由上面的push指令解析可以看出
subl$4,%esp
movl$8,(%esp)
就是
pushl$8linux内核占用cpu高,%esp
所以得到的最终状态也是
3)第三个案例
最终堆栈状态如右图所示:
3、从C语言到可执行程序
1).c的C语言文件通过gcc编译生成.asm的汇编语言文件
2).asm的汇编文件通过gas汇编生成.o的目标文件(obj文件,可链接)
3)再通过链接生成可执行文件,装载到显存当中进行执行。
4、从C语言到汇编语言linux内核占用cpu高,再从汇编语言到C语言
C语言如下:
int g(int x)
{
return x+3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8)+1;
}
对应的汇编语言为:
...
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
...
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
movl %ebp,%esp
popl %ebp
ret
...
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $8, (%esp)
call f
addl $1, %eax
movl %ebp,%esp
popl %ebp
ret
...
汇编语言如下:
对应的C语言为:
int g(int x)
{
return x+8;
}
int main(void)
{
return g(8)-8;
}
5、函数的堆栈框架
函数调用使用call指令。
1)callxxx
在call之前,会先把函数须要的参数压栈。
执行call时,会将当前的eip压栈,之后eip的值为xxx的入口地址。
2)步入xxx时
pushl%ebp
movl%esp,%ebp
初始化栈堆,进行栈对齐。
3)退出xxx时
movl%ebp,%esp
popl%ebp
ret
恢复调用函数前的现场。
6、用户态和内核态
在高执行级别下,代码可以执行特权指令,访问任意的化学地址linux开发培训,这些cpu执行级别就对应着内核态。
而在相应的低执行级别下,代码的掌控范围会遭到限制,只能在对应级别容许的范围内活动。
eg:intelx86cpu有4中执行级别0-3,Linux只使用其中的0级和3级分别来表示内核态和用户态。
cs寄存器的最低两位表示了当前代码的特权级
CPU每条指令的读取都是通过cs:eip这两个寄存器,其中cs是代码段选择寄存器,eip是偏斜量寄存器。
上述判定由硬件完成。
通常来说在Linux中,地址空间是一个明显的标志:0xcxc0000000以上的地址空间只能在内核态下访问,0x00000000-0xbfffffff的地址空间在两种状态下都可以访问。(此处的地址空间为逻辑地址,而非化学地址。)
7、系统调用
操作系统为用户态进程与硬件设备进行交互提供了一组插口——系统调用。
意义:把用户从底层的硬件编程中解放下来,极大地增强了系统安全性,使用户程序具有可移植性。
应用编程插口(API)和系统调用是不一样的。API只是一个函数定义,系统调用通过软中断向内核发出一个明晰的恳求。
Libc库定义的一些封装类库(惟一的目的就是发布系统调用),通常每位系统调用对应一个封装类库,库再用封装类库定义出给用户的API。所以,不是每位API都对应一个特定的系统调用鸟哥的linux私房菜,API可能直接提供用户态的服务,一个单独的API可能调用多个系统调用,不同的API可能调用了同一个系统调用。大部份的封装类库返回一个整数,其值的涵义依赖于对应的系统调用,返回值-1通常表示内核不能满足进行的恳求。Libc中定义的errno变量包含了特定的出错码。
关系图如下:
当用户态进行调用一个系统调用的时侯,CPU切换到内核态并开始执行一个内核函数。Linux中是通过执行int$0x80来执行一个系统调用的。这条汇编指令形成向量为128的编译异常。
内核实现了好多不同的系统调用,然而进程必须指明须要那个系统调用,这须要传递一个名为系统调用号的参数,通常用eax寄存器。
8、可执行文件的加载
9、进程、轻量级进程、线程和内核线程
CPU(单CPU系统)是一条指令一条指令执行的。
进程是执行上下文、定义一个执行流,有非常权限的进程管理其他进程,也就是分配CPU执行时间。
轻量级进程是为了支持多线程(一个进程中有多个执行流)而使多个进程共享一些资源,从操作系统的角度看没有线程,全部通过进程来分配资源并进行调度管理,但从多线程应用开发者的角度来看,它虽然在同一个进程中创建了多个执行流(线程)。
内核线程并不是线程,而是一个特殊一点的进程,仅工作在内核态,通常是一些服务程序为了防止内核态和用户态切换的开支,而普通进程通常按照须要通过系统调用在内核态和用户态之间反复切换。
为了管理进程,内核必须对每位进程进行清晰的描述。进程描述符提供了内核所需了解的进程信息。
进程描述符图示:
进程状态转换图:
进程数组:
进程数组的操作:
进程之间的亲属关系:
进程的内核栈堆:
Linux为每位进程分配一个8KB大小的显存区域,用于储存该进程两个不同的数据结构。1)Thread_info;2)进程的内核堆栈。
进程处于内核态时使用,不同于用户态堆栈。
10、进程的切换
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并回复先前挂起的某个进程的执行。
进程上下文,包含用户地址空间(程序代码、数据、用户堆栈等),控制信息(进程描述符、内核堆栈等),硬件上下文(主要是寄存器)。
schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换(prev和next)。
剖析switch_to宏,哪些时侯next进程真正开始执行?
首先在当前进程prev的内核栈中保存esi,edi及ebp寄存器的内容。
然后将prev的内核堆栈指针ebp存入prev->thread.esp中。
把将要运行进程next的内核栈指针next->thread.esp置入esp寄存器中
将popl指令所在的地址保存在prev->thread.eip中,这个地址就是prev下一次被调度
通过jmp指令(而不是call指令)转入一个函数__switch_to()
恢复next上次被调离时推进堆栈的内容。从现在开始,next进程就成为当前进程而真正开始执行。
当__switch_to正常返回时,发生了哪些事情?
切换了内核堆栈和硬件上下文。
__switch_to是如何传参的?
switch_to(prev,next,last), 可以看到last就是prev
调用方法如下:进程A->进程B,switch_to(A,B,A)主要有三个参数:
输入参数两个:prev:切换前的进程 next:切换后的进程
输出参数一个:last:切换前进程 注意这三个变量都是局部变量,
在系统栈中,所以切换到另一进程后变量的值不会改变。
进程a切换b之前,eax的值为prev,也就是a,edx的值为next,也就是b,ebx的值为prev,也就是a
当不考虑第三个参数时,从c切换成a,内核栈切换成a的栈,这时a中的prev和nexxt
分别指向a和b,进程c的引用丢失了。
这时第三个参数就派上用场了。
c切换进程a后,将c存入eax中,切换到a后,由于输出部"=a" (last)会将eax的值
写入last中,也就是prev中,所以此时prev和next的值就是c和b了。
11、操作系统构架和执行过程
操作系统:内核(进城管理、进程调度、进程间通信机制、内存管理、中断异常处理、文件系统、I/O系统、网络部份)+其他程序(库函数、shell程序、系统程序等)
操作系统的目的:与硬件交互,管理硬件资源。为用户程序提供一个良好的执行环境。
在CPU执行指令的角度来看: