当前位置:文档之家› 最新Linux复习大汇总

最新Linux复习大汇总

最新Linux复习大汇总
最新Linux复习大汇总

第一章概述

1、什么是POSIX标准,为什么现代造作系统的设计必须遵循POSIX标准?

答:POSIX表示可移植操作系统接口(Portable Operating System Interface)。

POSIX是在Unix标准化过程中出现的产物。

POSIX 1003.1标准定义了一个最小的Unix操作系统接口。

任何操作系统只有符合这一标准,才有可能运行Unix程序。

2、什么是GNU?Linux与GNU有什么关系?

答:GNU是GNU Is Not Unix的递归缩写,是自由软件基金会的一个项目。

GNU项目产品包括emacs编辑器、著名的GNU C和Gcc编译器等,这些软件叫做GNU 软件。

GNU软件和派生工作均适用GNU通用公共许可证,即GPL(General Public License)。

Linux的开发使用了众多的GUN工具。

3、Linux系统由哪些部分组成?Linux内核处于什么位置?

答:(1)符合POSIX标准的操作系统内核、Shell和外围工具。(2)C语言编译器和其他开发工具及函数库。(3)X Window窗口系统。(4)各种应用软件,包括字处理软件、图象处理软件等。

Linux内核是所有Linux 发布版本的核心。

第二章内存寻址

1、在80x86的寄存器中,哪些寄存器供一般用户使用?哪些寄存器只能被操作系统使用?答:通用寄存器EAX,EBX,ECX,EDX,EBP(基址指针),ESP(堆栈指针),ESI(扩展源指针),EDI(扩展目的指针),还有EFLAGS(标志寄存器).

专供操作系统使用的寄存器:IDTR中断描述符寄存器,GDTR全局描述符表寄存器,LDTR 局部描述符表寄存器,TaskRegisters任务寄存器,DebugRegister调试寄存器,ControlRegister 控制寄存器,Model-SpecificRegisters模型专用寄存器。

3、请用C语言描述段描述符表。

答:#include

#include

#define BASE_H 0xff00

#define BASE_M 0x00ff

#define BASE_L 0xffff

#define LIMIT_H 0x000f

#define LIMIT_L 0xffff

#define GRANULARITY 0x0080

#define SEGMENT_P 0x8000

#define SYSTEM_TYPE 0x1000

#define DPL 0x6000

#define TYPE_E 0x0800

#define TYPE_ED_C 0x0400

#define TYPE_RW 0x0200

#define TYPE_A 0x0100

struct {

unsigned char base_high;

unsigned char g : 1;

unsigned char other : 3;

unsigned char limit_high : 4;

unsigned char P : 1;

unsigned char dpl : 2;

unsigned char S : 1;

unsigned char E : 1;

unsigned char ED_C : 1;

unsigned char RW : 1;

unsigned char A : 1;

unsigned char base_middle;

unsigned short base_low;

unsigned short limit_low;

} gdt_t;

int main(int argc, char *argv[])

{

// int gdt[4] = {0x0fff, 0x0000, 0x9a00, 0x00c0} ; // 0x08

// int gdt[4] = {0x7fff, 0x0000, 0x9a01, 0x00c0} ; // 0x10

// int gdt[4] = {0x0002, 0x8000, 0x920b, 0x00c0} ; // display memory

// int gdt[4] = {0xffff, 0xffff, 0xffff, 0xffff} ; // test

int gdt[4] = {0x03ff, 0x0000, 0xf201, 0x00c0} ; // ldt0 unsigned int base, limit;

unsigned int granularity;

unsigned int present, dpl, system_type, segment_type; unsigned int type_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=code or data)\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, data segment\n");

printf("ED=0, data segment\n");

printf("ED=1, stack segment\n");

printf("W=0, not writable\n");

printf("W=1, writable\n");

printf("\n");

printf("E=1, code segment\n");

printf("C=0, ingore privilege\n");

printf("C=1, use privilege\n");

printf("R=0, not readable\n");

printf("R=1, readable\n");

return EXIT_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:

struct task_struct {

}传统上这样的数据结构被叫做进程控制块PCB(process control blaock)

系统为了管理进程设置的一个专门的数据结构,用它来记录进程的外部特征,描述进程的运动变化过程。系统利用PCB来控制和管理进程,所以PCB是系统感知进程存在的唯一标志。进程与PCB是一一对应的。

包含的基本信息:

(1)状态信息-描述进程动态的变化。(2)链接信息-描述进程的父/子关系。(3)各种标识符

-用简单数字对进程进行标识。(4)进程间通信信息-描述多个进程在同一任务上协作工作。

(5)时间和定时器信息-描述进程在生存周期内使用CPU时间的统计、计费等信息。(6)调度信息-描述进程优先级、调度策略等信息。(7)文件系统信息-对进程使用文件情况进行记录。(8)虚拟内存信息-描述每个进程拥有的地址空间。(9)处理器环境信息-描述进程的执行环境(处理器的寄存器及堆栈等)

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,同时统计系统中进程的个数。答:static int print_pid(void)

{ struct task_struct *task,*p;

struct list_head *pos;

int count = 0;

printk("Hello World enter begin:\n");

task = &init_task;

list_for_each(pos,struct task_struct,tasks);

{ count++;

printk("%d->%s\n",p->pid,p->comm);

}

printk("the number of process is:%d\n",count);

return 0;

}

7、什么是写时复制技术,这种技术在什么情况下最能发挥其优势?

答:父进程和子进程共享页面而不是复制页面。然而,只要页面被共享,它们就不能被修改。无论父进程和子进程何时试图写一个共享的页面,就产生一个错误,这时内核就把这个页复制到一个新的页面中并标记为可写。原来的页面仍然是写保护的:当其它进程试图写入时,内核检查写进程是否是这个页面的唯一属主;如果是,它把这个页面标记为对这个进程是可写的。采用这种技术,显然只有预测到将要修改的页才会被复制,而且必须被复制,不然的话,就会破坏父进程的程序执行。

9、init内核线程与init进程是一回事吗?它们有什么本质的区别?

答:(1)init()函数是内核代码的一部分,在内核态运行,是独立的可执行代码的一部分。

(2)init进程在Linux操作系统中是一个具有特殊意义的进程,它是由内核启动并运行的第一个用户进程,因此它不是运行在内核态,而是运行在用户态。它的代码不是内核本身的一部分,而是存放在硬盘上可执行文件的映象中,和其他用户进程没有什么两样。

10、用fork写一个简单的测试程序,从父进程和子进程中打印信息。信息应该包括父进程和子进程的PID。执行程序若干次,看两个信息是否以同样的次序打印。

答:#include

int main(void)

{

pid_t pid;

pid = fork();

if(pid < 0)

{

printf("error");

return 0;

}

else if(pid == 0)

{

printf("this is chile process with pid of %d\n",getpid());

}

相关主题
文本预览
相关文档 最新文档