第七章
在Linux0.01内核中的include/linux/sched.h文件中,定义了进程的不同状态,
#define TASK_RUNNING 0 表示进程在"Ready List"中,这个进程除了CPU以外
,获得了所有的其他资源
#define TASK_INTERRUPTIBLE 1 进程在睡眠中,正在等待一个信号或者一个资源
#define TASK_UNINTERRUPTIBLE 2 进程等待一个资源,当前进程在“Wait Queue“
#define TASK_ZOMBIE 3 僵尸进程(没有父进程的子进程)
#define TASK_STOPPED 4 标识进程在被调试
进程控制表中的每一项都是一个task_struct结构,而task_struct结构中存储各种低级和高级的信息,包括从一些硬件设备的寄存器复制到进程的工作目录的链接点。
进程控制表既是一个数组,又是一个双向链表,同时又是一个树。其实现是一个包括多个指针的静态数组。此数组的长度NR_TASKS
#define NR_TASKS 64
#define HZ 100
数组中的结构则保存在系统预留的内存页中,定义如下:
#define FIRST_TASK task[0]
#define LAST_TASK task[NR_TASKS-1]
系统启动后,内核通常作为某一个进程的代表。一个指向task_struct的全局指针变量current用来记录正在运行的进程。变量current只能由kernel/sched.c中的进程调度改变。
内
核线程在Linux2.0.0的核心中就可以被看到,例如在linux-2.0.1\linux\arch\alpha\kernel\entry.S中
的代码。Linux系统使用系统调用fork()来创建一个进程,用exit()来结束一个进程。fork()和exit()的源程序保存在
kernel/fork.c和kernel/exit.c中。fork()主要任务是初始化要创建进程的数据结构,其主要的步骤有:
(1)申请一个空闲的页面来保存task_struct。
(2)查找一个空的进程槽(find_empty_process())。
(3)为kernel_stack_page申请另一个空闲的内存页作为堆栈。
(4)将父进程的LDT表复制给子进程。
(5)复制父进程的内存映射信息。
(6)管理文件描述符和链接点。使用fork()创建一个进程后,程序的俩个复制都在运行。通常一个复制使用exec()调用系统的应用程序。
进程的调度(schedule()函数)
用户级线程(User-level threads) 是由与应用程序链接的线程库实现。核心不知道线程的
存在,也就不能独立调度这些线程了。
多任务系统可以划分为单处理器多任务系统和多处理器任务系统。
Linux0.01的核心进程源代码主要包括sched.c,fork.c,kill.c等。
sched.c是Linux的主要核心文件,它实现了Linux的进程调度功能,包括实现进程的各种状
态:睡眠(sleep_on),唤醒(wakeup),调度(schedule)等。
fork.c包含了实现系统调用fork()的辅助功能函数。
kill.c主要实现了系统调用kill(),kill()的作用主要是杀死一个进程。
system_call.s包含了系统的低级处理函数,主要都是一些汇编处理程序。
第八章
设备管理系统通常是设备驱动程序和操作系统的其余部分和应用程序的唯一接口。
设备管理系统需要实现如下的功能:
(1)隔离设备驱动程序和操作系统核心。
(2)隔离硬件和用户程序。
一般操作系统的设备管理程序具有如下特性:
(1)异步I/O(Asynchronous I/O)
(2)即插即用(Plug and Play)
在Unix下可以使用read()和write()来读和写设备,用ioctl()来对设备进行设置;而在Windows使用ReadFile()和WrintFile()来读和写设备,用DeviceIoControl()来对设备进行设置。
在Linux系统中俩种典型的情况:读RAMDISK和读IDE设备。
对于read请求,如果read一个RAMDISK,典型的过程如下:
(1)从RAMDISK内存空间复制内存到用户空间(user space)。
(2)结束调用,返回用户空间。
一个IDE驱动程序,典型的read处理过程如下:
(1)接受一个read请求,然后把这个请求放到I/O队列中。
(2)如果I/O队列为空,将这个读写磁盘扇区的请求发送到磁盘驱动器。
(3)将调用read系统调用的进程切换为SLEEP状态。
(4)当磁盘驱动器完成磁盘扇区读操作后,会发出一个中断。IDE驱动程序接收到这个中断后,把读出的数据从磁盘控制器传送到核心的buffer中,然后把数据复制到用户空间,接下来唤醒调用进程。
(5)当调用浚进程被唤醒时,系统的控制权回到用户空间的用户进程。
CPU
的内部中断又可以叫异常,异常的主要作用是报告一些程序运行中的错误和处理缺页中断(page_fault)。这些异常都是通过
set_trap_gate宏来设置的。set_trap_gate宏是定义在include/asm/system.h文件中的一个宏,被用来设置
CPU的trap,起定义如下:
#define set_trap_gate(n,addr) \
_set_gate(&idt[n],15,0,addr)
在Linux0.01中,中断处理主要做了俩个部分的工作:
(1)trap.s将各个CPU的trap信息填入到idt中;
(2)asm.s处理当出现trap的时候后续的一系列处理。
在Linux0.01中处理的外部中断有时钟中断(0x20),串行通信口中断(0x23和0x24),硬盘中断(0x2e),键盘中断(0x21),这些中断具体的处理函数在文件rs_io.s,hd.c和keyboard.s中。
如果多个设备共享一个中断,那么每当一个设备产生一个中断时,CPU会执行所有的isr中断服务程序。具体的一个完整的中断产生和处理流程是:
(1)产生中断
(2)CPU应答
(3)查找idt中的对应向量
(4)在gdt中查找idt项的代码段
(5)对比当前的cpl和描述符的dpl看是否产生越级保护
(6)检查是否发生特权级的变化,如果是就保存ss和esp,否则不保存
(7)保存eflags,cs,eip和错误码
(8)将idt对应描述地址装入cs和eip中以便执行
(9)执行irp_interup
(10)执行do_irp
(11)循环执行isr
(12)中断返回。
trap.c处理了硬件中断和运行中可能产生的错误。
asm.s包含了处理低级硬件错误的代码,asm.s也使用TS位来处理协处理器错误。
ISR(Interrupt Service Routine,ISR),中断处理程序。
x86体系结构是一个中断驱动的系统,外部发生的事件总是通过中断服务程序来进行处理。
在x86体系结构中,中断处理程序的入口地址是保留在系统的IDT(Interrupt Descriptor Table,中断描述表)中的。
中断处理程序和普通程序的区别是:中断处理程序必须非常简单,而且需要处理CPU的状态
。ISR在结束时,都必须调用"Interrupt Return(IRET)",而普通应用程序在结束时调用的是"Return (RET)"或者"Far Return(RETF)"。
BIOS数据区提供了大量的硬件设备信息,通过读取BIOS的数据,可以方便地了解计算机的硬件设备情况。
在IMB AT和IBMPS/2键盘系统中,CPU并不直接和键盘进行通信,而是通过一个8042芯片或者其他与之兼容的芯片。键盘本身也有自己的芯片(Intel8084及其兼容芯片)。
console.c主要实现了控制台功能,包含con_init()和con_write()函数。
serial.c实现了rs232串口通信功能,主要函数是:rs_write(),rs_init()以及相关的中断功能。
第九章
Linux系统继承了Unix中"everything is a file"的思想,使用文件系统接口,从而可以控
制Linux系统中的所有设备。通过引入VFS(Virtual File System)的概念,可以使Linux文件系统的主要部分和具体的物理文件系统类型无关。
对
硬盘进行访问的基础是对硬盘的磁盘扇区进行寻址。通常,对于硬盘的寻址使用CHS(Cylinder/Head/Sector)参数。CHS就是使用柱面
数(Cylinders),磁头数(Heads),和扇区数(Sectors per track)来定位硬盘驱动器任何一个扇区的方法。
Cylinders表示硬盘每一面盘片上有几条磁道,最大为1024(用10个二进制位存储).
Heads表示硬盘总共有几个磁头,也就是有几面磁片,最大为256(用8个二进制位存储)。
Sectors per track表示每一条磁道上有几个扇区,最大为63(用6个二进制位存储)。
每一个扇区一般是512B所以磁盘最大容量为
256*1024*63*512/1048576=8064GB (1M=1048576B)
BIOS INT 13h调用是BIOS提供的磁盘基本输入输出中断调用,它可以完成磁盘(包括硬盘和软盘)的复位,读写,校验,定位,诊断,格式化等功能。它使用的就是CHS寻址方式,因此最大能访问8GB左右的硬盘。
典型的扇区存放形式:
扇区在磁道上连续存放;带磁盘内部Cache的现代磁盘。
Unix文件系统结构
Boot block |Super block |Inode table...........|Data block zone
引导块(Boot block)通常位于文件卷最开始的第一扇区,这512B是文件系统的引导代码,为根文件系统所特有,其他文件系统中这512B为空。
超级块(Sper block)紧跟引导块之后,用于描述文件系统的结构,如i节点长度,文件系统大小等信息。
i节点表(Inode table)存放在超级块之后,其长度由超级块中i节点长度字段决定,起作用是用来描述文件的属性,长度,属主,属组,数据块表等信息。
数据区(Data block zone)跟在i节点表后面,用于存放文件的数据。
在Unix系统中,一个普通的文件通常由俩部分组成:i节点+数据块
用fsck可以修正文件系统的不一致。
fsck - check and repair a Linux file system
日志文件系统(Log-Structured File System)的设计思想是跟踪文件系统的变化而不是文件系统的内容,所有对文件系统的更新都被记录在日志中。
Linux文件系统其实可以分为三个部分,第一部分叫VFS。这是Linux文件系统对外的接口,
任何要使用文件系统的程序都必须经由这层接口来使用它。另外俩部分属于文件系统的内部,其中一个是Cache,另一个就是真正最底层的文件系统,像Ext2,FAT之类的。
在文件系统里的每一个文件,系统都给它一个inode,只要inode不一样,就表示这俩个文件不相同,inode是由VFS定义的。
一个硬盘最多可以有8个分区(partition),其中4个是主分区(Primary partition),另4个则是扩展分区(extended partition)。除了分区,硬盘第一个扇区称为MBR(Master Boot Record,主启动录)。
文件系统的一些过程:
创建一个文件,其步骤如下:
(1)分配和初始化inode
(2)将inode和文件名的对应关系写入到文件目录表项中(通常在当前的工作目录)
(3)将上述数据写入磁盘。
修改一个磁盘文件,起步骤如下:
(1)将此文件的inode装载到内存中
(2)分配空闲的磁盘块(将这些磁盘块在freelist中标记为已经分配)
(3)修改这个文件的inode,指向这个新分配的磁盘块
(4)将用户的数据写入这个磁盘块
(5)将这个文件所有的修改都写入磁盘。
删除一个磁盘文件,其步骤如下:
(1)将此文件的inode装载到内存中。
(2)依据inode数据查找到将要删除的数据所在的磁盘块
(3)标记这个磁盘块为空闲
(4)将这个文件所有的修改都写入磁盘。
在系统启动时,只执行一次的函数sys_setup读取所有的IDE硬盘上的分区表。在kernel/hd.c源程序中包含对各种IDE接口的驱动代码。此外rw_hd()函数完成了从各种IDE设备上读取
和写入数据块的功能。
在Linux0.01的文件系统中hd.c()代码演示了从IDE设备上读取和写入数据块,而fs/block_dev.c中包含了对块设备文件系统的具体支持。
kernel/hd.c处理IDE中断和数据块写入,读出队列
fs/block_dev.c使用hd.c中提供的功能,例如:block_write()来向硬件驱动器中写入或者读出数据
fs/read_write.c中包含了sys_read(),sys_write(),and sys_lseek()的代码。
super.c文件中包含了Linux0.01的文件系统处理超级块表(super-block tables)的功能。
read_write.c文件包含了Linux0.01文件系统中对字符设备和块设备的读操作和写操作的实现过程。
open.c包含了Linux0.01中打开文件的操作过程。
inode.c包含了Linux0.01中对文件系统inode表的维护过程。
buffer.c实现了文件系统中的缓冲功能。
bitmap.c包含了处理文件系统中inode和文件系统中位图的功能。
阅读(1604) | 评论(0) | 转发(0) |