Linux复习汇总.docx
《Linux复习汇总.docx》由会员分享,可在线阅读,更多相关《Linux复习汇总.docx(19页珍藏版)》请在冰豆网上搜索。
![Linux复习汇总.docx](https://file1.bdocx.com/fileroot1/2022-11/30/24c3b86c-9b48-4a24-97b4-3cd1d6bc1bf8/24c3b86c-9b48-4a24-97b4-3cd1d6bc1bf81.gif)
Linux复习汇总
第一章概述
1、什么是POSIX标准,为什么现代造作系统的设计必须遵循POSIX标准?
答:
POSIX表示可移植操作系统接口(PortableOperatingSystemInterface)。
POSIX是在Unix标准化过程中出现的产物。
POSIX1003.1标准定义了一个最小的Unix操作系统接口。
任何操作系统只有符合这一标准,才有可能运行Unix程序。
2、什么是GNU?
Linux与GNU有什么关系?
答:
GNU是GNUIsNotUnix的递归缩写,是自由软件基金会的一个项目。
GNU项目产品包括emacs编辑器、著名的GNUC和Gcc编译器等,这些软件叫做GNU软件。
GNU软件和派生工作均适用GNU通用公共许可证,即GPL(GeneralPublicLicense)。
Linux的开发使用了众多的GUN工具。
3、Linux系统由哪些部分组成?
Linux内核处于什么位置?
答:
(1)符合POSIX标准的操作系统内核、Shell和外围工具。
(2)C语言编译器和其他开发工具及函数库。
(3)XWindow窗口系统。
(4)各种应用软件,包括字处理软件、图象处理软件等。
Linux内核是所有Linux发布版本的核心。
4、了解Linux内核源代码结构,说明/kernel目录下包含哪些文件?
答:
第二章内存寻址
1、在80x86的寄存器中,哪些寄存器供一般用户使用?
哪些寄存器只能被操作系统使用?
答:
通用寄存器EAX,EBX,ECX,EDX,EBP(基址指针),ESP(堆栈指针),ESI(扩展源指针),EDI(扩展目的指针),还有EFLAGS(标志寄存器).
专供操作系统使用的寄存器:
IDTR中断描述符寄存器,GDTR全局描述符表寄存器,LDTR局部描述符表寄存器,TaskRegisters任务寄存器,DebugRegister调试寄存器,ControlRegister控制寄存器,Model-SpecificRegisters模型专用寄存器。
2、在保护模式下,MMU如何把一个虚地址转换为物理地址?
答:
分段机制把一个虚拟地址转换为线性地址;接着,分页机制把一个线性地址转换为物理地址。
3、请用C语言描述段描述符表。
答:
#include
#include
#defineBASE_H0xff00
#defineBASE_M0x00ff
#defineBASE_L0xffff
#defineLIMIT_H0x000f
#defineLIMIT_L0xffff
#defineGRANULARITY0x0080
#defineSEGMENT_P0x8000
#defineSYSTEM_TYPE0x1000
#defineDPL0x6000
#defineTYPE_E0x0800
#defineTYPE_ED_C0x0400
#defineTYPE_RW0x0200
#defineTYPE_A0x0100
struct{
unsignedcharbase_high;
unsignedcharg:
1;
unsignedcharother:
3;
unsignedcharlimit_high:
4;
unsignedcharP:
1;
unsignedchardpl:
2;
unsignedcharS:
1;
unsignedcharE:
1;
unsignedcharED_C:
1;
unsignedcharRW:
1;
unsignedcharA:
1;
unsignedcharbase_middle;
unsignedshortbase_low;
unsignedshortlimit_low;
}gdt_t;
intmain(intargc,char*argv[])
{
//intgdt[4]={0x0fff,0x0000,0x9a00,0x00c0};//0x08
//intgdt[4]={0x7fff,0x0000,0x9a01,0x00c0};//0x10
//intgdt[4]={0x0002,0x8000,0x920b,0x00c0};//displaymemory
//intgdt[4]={0xffff,0xffff,0xffff,0xffff};//test
intgdt[4]={0x03ff,0x0000,0xf201,0x00c0};//ldt0
unsignedintbase,limit;
unsignedintgranularity;
unsignedintpresent,dpl,system_type,segment_type;
unsignedinttype_e,type_ed_c,type_rw,type_a;
base=(gdt[3]&BASE_H)<<16;
base+=(gdt[2]&BASE_M)<<16;
base+=gdt[1]&BASE_L<<0;
limit=(gdt[0]&LIMIT_L)<<0;
limit+=(gdt[3]&LIMIT_H)<<16;
granularity=(gdt[3]&GRANULARITY)>>7;
present=(gdt[2]&SEGMENT_P)>>15;
dpl=(gdt[2]&DPL)>>13;
system_type=(gdt[2]&SYSTEM_TYPE)>>12;
type_e=(gdt[2]&TYPE_E)>>11;
type_ed_c=(gdt[2]&TYPE_ED_C)>>10;
type_rw=(gdt[2]&TYPE_RW)>>9;
type_a=(gdt[2]&TYPE_A)>>8;
printf("base\t\t0x%x\t%d\n",base,base);
printf("limit\t\t0x%x\t%d\n",limit,limit);
printf("\n");
printf("G\t\t%d\t(0=bype,1=4KB)\n",granularity);
printf("\n");
printf("P\t\t%d\n",present);
printf("DPL\t\t%d\n",dpl);
printf("S\t\t%d\t(0=system,1=codeordata)\n",system_type);
printf("TYPE_E\t\t%x\n",type_e);
printf("TYPE_ED_C\t%x\n",type_ed_c);
printf("TYPE_RW\t\t%x\n",type_rw);
printf("TYPE_A\t\t%x\n",type_a);
printf("\n");
printf("E=0,datasegment\n");
printf("ED=0,datasegment\n");
printf("ED=1,stacksegment\n");
printf("W=0,notwritable\n");
printf("W=1,writable\n");
printf("\n");
printf("E=1,codesegment\n");
printf("C=0,ingoreprivilege\n");
printf("C=1,useprivilege\n");
printf("R=0,notreadable\n");
printf("R=1,readable\n");
returnEXIT_SUCCESS;
}
4、Linux是如何利用段机制又巧妙的绕过段机制的?
在内核代码中如何表示各种段,查找最新源代码并进行阅读和分析。
答:
IA32规定段机制是不可禁止的,因此不可能绕过它直接给出线性地址空间的地址。
万般无奈之下,Linux的设计人员干脆让段的基地址为0,而段的界限为4GB,这时任意给出一个偏移量,则等式为“0+偏移量=线性地址”,也就是说“偏移量=线性地址”。
另外由于段机制规定“偏移量<4GB”,所以偏移量的范围为0H~FFFFFFFFH,这恰好是线性地址空间范围,也就是说虚拟地址直接映射到了线性地址,我们以后所提到的虚拟地址和线性地址指的也就是同一地址。
看来,Linux在没有回避段机制的情况下巧妙地把段机制给绕过去了。
另外,由于IA32段机制还规定,必须为代码段和数据段创建不同的段,所以Linux必须为代码段和数据段分别创建一个基地址为0,段界限为4GB的段描述符。
不仅如此,由于Linux内核运行在特权级0,而用户程序运行在特权级别3,根据IA32的段保护机制规定,特权级3的程序是无法访问特权级为0的段的,所以Linux必须为内核和用户程序分别创建其代码段和数据段。
这就意味着Linux必须创建4个段描述符——特权级0的代码段和数据段,特权级3的代码段和数据段。
5、为什么在设计两级页表的线性地址结构时,给页目录和页表各分配10位?
如果不是这样,举例说明会产生什么样的结果?
答:
所谓两级页表就是对页表再进行分页。
第一级称为页目录,其中存放的是关于页表的信息。
4MB的页表再次分页(4MB/4K)可以分为1K个页,同样对每个页的描述需要4个字节,于是可以算出页目录最多占用4KB个字节,正好是一个页,其示意图如2.9所示。
页目录共有1K个表项,于是,线性地址的最高10位(即22位~31位)用来产生第一级的索引。
两级表结构的第二级称为页表,每个页表也刚好存放在一个4K字节的页中,包含1K个字节的表项。
第二级页表由线性地址的中间10位(即21位~12位)进行索引,最低12位表示页内偏量。
举例:
如果页目录占用31~24位,页表项占用23~12位,偏移地址占11~0位,则有2^8个页表项,一个页面有2^12(4k)个表项,每个表项大小为4字节,2^12的范围为4页面,而不是以一个页面为准。
6、深入理解图2.12,并结合图叙述线性地址到物理地址的转换。
答:
第一步,用32位线性地址的最高10位第31~22位作为页目录项的索引,将它乘以4,与CR3中的页目录的起始地址相加,获得相应目录项在内存的地址。
第二步,从这个地址开始读取32位页目录项,取出其高20位,再给低12位补0,形成的32位就是页表在内存的起始地址。
第三步,用32位线性地址中的第21~12位作为页表中页表项的索引,将它乘以4,与页表的起始地址相加,获得相应页表项在内存的地址。
第四步,从这个地址开始读取32位页表项,取出其高20位,再将线性地址的第11~0位放在低12位,形成最终32位页面物理地址。
第三章进程
1、什么是进程控制块?
它包含哪些基本信息?
答:
对进程进行全面描述的数据结构,Linux中把对进程的描述结构叫做task_struct:
structtask_struct{
}传统上这样的数据结构被叫做进程控制块PCB(processcontrolblaock)
系统为了管理进程设置的一个专门的数据结构,用它来记录进程的外部特征,描述进程的运动变化过程。
系统利用PCB来控制和管理进程,所以PCB是系统感知进程存在的唯一标志。
进程与PCB是一一对应的。
包含的基本信息:
(1)状态信息-描述进程动态的变化。
(2)链接信息-描述进程的父/子关系。
(3)各种标识符-用简单数字对进程进行标识。
(4)进程间通信信息-描述多个进程在同一任务上协作工作。
(5)时间和定时器信息-描述进程在生存周期内使用CPU时间的统计、计费等信息。
(6)调度信息-描述进程优先级、调度策略等信息。
(7)文件系统信息-对进程使用文件情况进行记录。
(8)虚拟内存信息-描述每个进程拥有的地址空间。
(9)处理器环境信息-描述进程的执行环境(处理器的寄存器及堆栈等)
2、Linux内核的状态有哪些?
请画出状态转换图,查看最新源代码,以确认有哪些状态。
答:
就绪态(TASK_RUNNING);睡眠(或等待)态:
浅度睡眠态(TASK_INTERRUPTIBLE),深度睡眠态(TASK_UNINTERRUPTIBLE);暂停状态(TASK_STOPPED);僵死状态(TASK_ZOMBIE)
3、Linux的进程控制块如何存放?
为什么?
假设ESP中存放的是栈顶指针,请用三句汇编语句描述如何获得current的PCB的地址。
答:
当进程一进入内核态,CPU就自动设置进程的内核栈。
这个栈位于内核的数据段上,为了节省空间,Linux把内核栈和一个紧挨近的PCB的小数据结构,thread_info放在一起,占用8kb的内存区。
因为这样可以节省空间,内核很容易从ESP寄存器的值获得,当前在CPU上正在运行的thread_info结构的地址。
movl$0xfffe000,%eax
andl%esp,%ecx
movl%ecx,p
4、PCB的组织方式有哪几种?
为什么要采取这些组织方式?
答:
(1)进程链表
(2)哈希表(3)就绪队列(4)等待队列
在一个系统中,通常可以拥有数十个、数百个乃至数千个进程,相应的就有这么多PCB。
为了能有效的对它们加以管理,应该用适当的方式将这些PCB组织起来。
5、请编写内核模块,打印系统中各进程的名字以及PID,同时统计系统中进程的个数。
答:
staticintprint_pid(void)
{structtask_struct*task,*p;
structlist_head*pos;
intcount=0;
printk("HelloWorldenterbegin:
\n");
task=&init_task;
list_for_each(pos,structtask_struct,tasks);
{count++;
printk("%d->%s\n",p->pid,p->comm);
}
printk("thenumberofprocessis:
%d\n",count);
return0;
}
7、什么是写时复制技术,这种技术在什么情况下最能发挥其优势?
答:
父进程和子进程共享页面而不是复制页面。
然而,只要页面被共享,它们就不能被修改。
无论父进程和子进程何时试图写一个共享的页面,就产生一个错误,这时内核就把这个页复制到一个新的页面中并标记为可写。
原来的页面仍然是写保护的:
当其它进程试图写入时,内核检查写进程是否是这个页面的唯一属主;如果是,它把这个页面标记为对这个进程是可写的。
采用这种技术,显然只有预测到将要修改的页才会被复制,而且必须被复制,不然的话,就会破坏父进程的程序执行。
9、init内核线程与init进程是一回事吗?
它们有什么本质的区别?
答:
(1)init()函数是内核代码的一部分,在内核态运行,是独立的可执行代码的一部分。
(2)init进程在Linux操作系统中是一个具有特殊意义的进程,它是由内核启动并运行的第一个用户进程,因此它不是运行在内核态,而是运行在用户态。
它的代码不是内核本身的一部分,而是存放在硬盘上可执行文件的映象中,和其他用户进程没有什么两样。
10、用fork写一个简单的测试程序,从父进程和子进程中打印信息。
信息应该包括父进程和子进程的PID。
执行程序若干次,看两个信息是否以同样的次序打印。
答:
#include
intmain(void)
{
pid_tpid;
pid=fork();
if(pid<0)
{
printf("error");
return0;
}
elseif(pid==0)
{
printf("thisischileprocesswithpidof%d\n",getpid());
}
else
{
printf("thisisparentprocesswithpidof%d\n",pid);
}
return0;
}
第四章内存管理
1、内核空间存放什么内容?
如何把其中的一个虚地址转换成物理地址?
答:
内核空间存放的是内核代码和数据。
2、什么是内核映像?
它存放在物理空间和内核空间的什么地方?
答:
内核空间由所有进程共享,其中存放的是内核代码和数据,即“内核映象”
Linux内核映像被装入在物理地址0x00100000开始的地方
内核映像在内核空间的起始地址就为0xC0100000。
3、用户空间划分为哪几部分?
用户程序调用malloc()分配的内存属于那一部分?
答:
分为堆栈段、BSS(未初始化的数据段)、数据段和代码段。
从数据段的顶部到堆栈段地址的下沿这个区间是一个巨大的空洞,这就是进程在运行时调用malloc()可以动态分配的空间,也叫动态内存或堆。
4、什么是虚存映射?
有哪几种类型?
答:
随着进程的运行,被引用的程序部分会由操作系统装入到物理内存,这种将映像链接到进程用户空间的方法被称为“虚存映射”,也就是把文件从磁盘映射到进程的用户空间,这样把对文件的访问转化为对虚存区的访问。
有两种类型的虚存映射:
(1)共享的:
有几个进程共享这一映射,也就是说,如果一个进程对共享的虚存区进行写,其它进程都能感觉到,而且会修改磁盘上对应的文件。
(2)私有的:
进程创建的这种映射只是为了读文件,而不是写文件,因此,对虚存区的写操作不会修改磁盘上的文件,由此可以看出,私有映射的效率要比共享映射的高。
除了这两种映射外,如果映射与文件无关,就叫匿名映射。
5、一个进程一般包含哪些虚存区?
举例说明。
6、说明mmap()系统调用的功能?
利用mmap()写一个拷贝文件的程序。
答:
mmap()系统调用时通过调用do_mmap内核函数来实现建立文件到显存的映射。
inti,fd1,fd2;
char*buf1,buf2;
fd1=open("data",0KDOMCY);
if(fd1&&fd2&&(fstar(fd);&starbuf1()=0)
{
if(lseek(fd)statbuf,st_size-1,seek_set)<0
{
perror"lseektarget");
exit(-1);
}
if(MAP_FAILED==buf())
{
perror("Lmapsource");
exit(-1);
}
if((map_FAFCE)==buf2)
{
perror("mmaptarget");
exit(-1);
}
mempy(buf2,buf1,(size-f)startbuf,st_size);
}
7、Linux是如何实现“请求调页”的?
答:
1)如果被访问的页不在内存,也就是说,这个页还没有被存放在任何一个物理页面中,那么,内核分配一个新的页面并将其适当地初始化,这种技术称为“请求调页”;
2)“请求调页”是一种动态内存分配技术,它将页面的分配推迟到不能再推迟为止,也就是说,一直推迟到进程要访问的页不在物理内存时为止,由此引起一个缺页异常;该技术的引入主要是因为进程开始运行时并不访问其地址空间中的全部地址。
8、试叙述伙伴算法的工作原理,并说明为什么伙伴算法可以消除外碎片?
答:
假设要求分配的块其大小为128个页面。
该算法先在块大小为128个页面的链表中查找,看是否有这样一个空闲块。
如果有,就直接分配;如果没有,该算法会查找下一个更大的块,具体地说,就是在块大小为256个页面的链表中查找一个空闲块。
如果存在这样的空闲块,内核就把这256个页面分为两等份,一份分配出去,另一份插入到块大小为128个页面的链表中。
如果在块大小为256个页面的链表中也没有找到空闲页块,就继续找更大的块,即512个页面的块。
如果存在这样的块,内核就从512个页面的块中分出128个页面满足请求,然后从384个页面中取出256个页面插入到块大小为256个页面的链表中。
然后把剩余的128个页面插入到块大小为128个页面的链表中。
如果512个页面的链表中还没有空闲块,该算法就放弃分配,并发出出错信号。
9、vmalloc()和kmalloc()有何区别?
编写内核模块程序,调用这两个函数以观察二者所分配空间位于不同的区域。
答:
在内核空间中调用kmalloc()分配连续物理空间,而调用vmalloc()分配非物理连续空间。
我们把kmalloc()所分配内核空间中的地址称为内核逻辑地址
把vmalloc()分配的内核空间中的地址称为内核虚拟地址
vmalloc()在分配过程中须更新内核页表
#include
#include
#include
#include
#include
unsignedchar*kmallocmen;
unsignedchar*vmallocmen;
MODULL_LICENSE("GPL");
staticint_initinit_mmshow(void)
{
pagemem=_get_free_page(GFP_KERNEL);
if(!
kmallocmen)
gotofail2;
printk(KERN_INFO"vmallocmen=0x%p\n",vmallocmen);
return0;
failB
fail1:
Kfree(kmallocmen);
fail2:
free_page(pagemon);
fail3:
return-1;
}
staticvoid_exitcleanup_mmshow(void)
{
Vfree(vmallocmen);
Kfree(kmallocmen);
free_page(pagemen);
}
module_init(imit_mmshow);
module_exit(cleanup_mmshow);
10、分析守护进程kswapd的运行时机,你认为怎样换出页面比较合理?
答:
为了避免在CPU忙碌的时候,也就是在缺页异常发生时,临时搜索可供换出的内存页面并加以换出,Linux内核定期地检查系统内的空闲页面数是否小于预定义的极限,一旦发现空闲页面数太少,就预先将若干页面换出,以减轻缺页异常发生时系统所承受的负担。
当然,由于无法确切地预测页面的使用,即使这样做了也还可能出现缺页异常发生时内存依然没有足够的空闲页面。
但是,预换出毕竟能减少空闲页面不够用的概率。
并且通过选择适当的参数(如每隔多久换出一次,每次换出多少页),可以使临时寻找要换出页面的情况很少发生。
为此,Linux内核设置了一个定期将页面换出的守护进程kswapd。
第五章中断和异常
1、什么是中