免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1083 | 回复: 0
打印 上一主题 下一主题

读Linux内核0.11完全注释 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-03-07 14:34 |只看该作者 |倒序浏览

1. Linux系统中的jiffs是指可编程定时芯片,如PC上的Intel8353设置的定时间隔.当系统每经过1个jiffs,就会调一次时钟中断处理程序(timer_interrupt).
2. Linux系统中,一个进程可以在内核装(kernel mode)和用户态(user mode)下执行,因此,Linux内核堆栈和用户堆栈分开.
3. 进程表项是一个task_struct任务结构指针.定义include/linux/sched.h中,通常称为PCB--进程控制块.保存着用于控制和管理进程的所有信息.包括进程当前运行的状态信息,信号,进程号,父进程号,运行时间累计值,正在使用的文件和本任务的局部描述符以及任务状态段信息.
struct task_struct {
long state; /* 任务的运行状态 */
long counter; /* 运行时间片,以jiffs递减计数 */
long priority; /* 运行优先数,开始时,counter = priority,值越大,表示优先数越高,等待时间越长. */
long signal; /* 信号.是一组位图,每一个bit代表一种信号. */
struct sigaction sigaction[32]; /* 信号响应的数据结构, 对应信号要执行的操作和标志信息 */
long blocked; /* 进程信号屏蔽码(对应信号位图) */
int exit_code; /* 任务执行停止的退出码,其父进程会取 */
unsigned long start_code; /* 代码段地址 */
unsigned logn end_code; /* 代码长度(byte) */
unsigned long end_data; /* 代码长度+数据长度(byte) */
unsigned long brk; /* 总长度(byte) */
unsigned long start_stack; /* 堆栈段地址 */
long pid; /* 进程号 */
long father; /* 父进程号 */
long pgrp; /* 父进程组号 */
long session; /* 会话号 */
long leader; /* 会话头(发起者) */
unsigned short uid; /* 用户id 号 */
unsigned short euid; /* 有效用户 id 号 */
unsigned short suid; /* 保存用户 id 号 */
unsigned short gid; /* 组标记号 (组id) */
unsigned short egid; /* 有效组 id */
unsigned short sgid; /* 保存的组id */
long alarm; /* 报警定时值 (jiffs数) */
long utime; /* 用户态运行时间 (jiffs数) */
long stime; /* 系统态运行时间 (jiffs数) */
long cutime; /* 子进程用户态运行时间 */
long cstime; /* 子进程系统态运行时间 */
long start_time; /* 进程开始运行时刻 */
unsigned short used_math; /* 是否使用了协处理器 */
int tty; /* 进程使用tty的子设备号. -1表示设有使用 */
unsigned short umask; /* 文件创建属性屏蔽位 */
struct m_inode *pwd; /* 当前工作目录 i节点结构 */
struct m_inode *root; /* 根目录i节点结构 */
struct m_inode *executable; /* 执行文件i节点结构 */
unsigned long close_on_exec; /* 执行时关闭文件句柄位图标志. */
struct file *filp[NR_OPEN]; /* 文件结构指针表,最多32项. 表项号即是文件描述符的值 */
struct desc_struc ldt[3]; /* 任务局部描述符表.0-空,1-cs段,2-Ds和Ss段 */
struct tss_struct tss; /* 进程的任务状态段信息结构 */
};
4.进程中的上下文是指进程执行时,CPU的所有寄存器中的值,进程的状态以及堆栈中的内容.
5.进程运行状态被称为进程状态.包括运行状态(TASK_RUNNIG),可中断睡眠状态(TASK_INTERRUPTIBLE),不可中断睡眠状态(TASK_UNINTERRUPTIBLE),暂停状态(TASK_STOPPED),僵死状态(TASK_ZOMBIE)

///////////////////////////////////////////////////////
1. 当如果一个进程在内核态执行时需要等系统的某个资源,些该进程就会调用sleep_on()或sleep_on_interrruptible()自愿地放弃CPU的使用权,
而让调度程序去执行其它进程.进程则进行睡眠状态(TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBEL).
2.为了避免进程切换时造成内核数据错误,内核在执行临界区代码时会禁止一切中断.
3.boot/目录中引导程序把内核加载到内存中,并让系统进入保护模式下运行后,就开始执系统初始化程序 init/main.c.该程序首先确定如何分配使用
系统物理内存,然后调用内核各部分的初始化函数分别对内存管理,中断处理,块设备和字符设备,进程管理以及硬盘和软盘硬件初始化处理.
4.完成初始化后,主程序把自己移动到任务0(进程0)中运行,并使用fork()调用首次创建出进程1.在进程1中,程序将继续进行应用环境的初始化并执行shell
登录程序.
5.Linux进程是抢占式的.被抢占的进程仍然处于TASK_RUNNIN状态,只是暂进没有被CPU运行.进程的抢占发生在进程处于用户态执行阶段,在内核态执行进是不能
被抢占的.
6.进程间切换采用一定的调度略,能有效地使用系统资源,Linux 0.11采用了基于优先级排队的调度策略.
7.调度程序,schedule().比较每个就绪态任务的运行时间,选一个counter值最大的运行.如果处于TASK_RUNNING状态进程的时间片都已经用完,系统会根据每个进程的
优先级值priority,对系统中所有进程(包括睡眠的进程)重新计算每个任务需要运行的时间片值counter.然后schedule()函数重新扫描任务数据中的所有处于TASK_RUNNIG状态,如此重复,直到选择出一个进程为止.最后调用switch_to()执行实际的进程切换操作. 如果此时没有其它进程可以运行,系统就会选择进程0运行.
进程0会调用pause()把自己置为可中断的睡眠状态并再次调用schedule().
8.当一个进程结束了运行或在半途中终止了运行,那么内核就需要释放该进程所占用的系统资源.这包括进程运行时打开的文件,申请的内存等.对于有子进程的进程,让init进程作为其所有子进程的父进程.如果进程是一个会话头进程且有控制终端,则释放控制终端,并向属于该会话的所有进程发送挂断信号SIGHUP,这通常终止该会话中的所有进程.然后把进程状态置为僵死状态TASK_ZOMBIE.并向其原父进程发送SIGCHLD信号,通知其某个子进程已经终止.最后do_exit()调用调度函数去执行其它进程.在进程被终止,父里程就会把子进程运行所使用的时间累加到自己进程中.
9.子进程执行期间,父进程通常用wait()或waitpid()函数等待其某个子进程终止.当等待的子进程被终止并处于僵死状态时,父进程就会把子进程运行所使用的时间累加到自己进程中,并释放子进程任务数据结构所占用的内存页面,并置空子进程在任务数据中占用的指针项.
10.几种地址以及它们之间的变换概念:
  a. 程序(进程)的逻辑地址(Logical Address):指程序产生与段相关的偏移地址部分.程序员仅需与逻辑地址打交道,而分段与分页机制对他是完全透明.
  b. CPU的线性地址(Linear Address):是逻辑地址到物理地址变换之间的中间层.把通过分段机制把段的基地址与偏移地址加在一起就生成一个线性地址.如果启用了分页机制,那么线性地址可以再经变换后产生一个物理地址.若没有启用分页机制,那么线性地址直接就是物理地址.
  c. 实际物理内存地址(Physical Address):是指出现在CPU外部总上的寻址物理内存的地址信号,是地址变换的最终结果地址. 若启用分页机制,线性地址会使用页目录和页表中的项变换成物理地址.
  d. 虚拟内存(Virtual Memory):计算机呈现出要比实际拥有的内存大得多的内存量.因些它允许程序编制并运行比实际系统拥有的内存大得多的程序.
11.CPU进行地址变换(映射)的主要目的是为了解决虚拟内存空间到物理内存空间的映射问题.虚拟内存的含义是指一种利用二级或外部存储空间,使程序能不受实际物理内存量限制而使用内存的一种方法.
12.虚拟内存空间管理的实现.当一个程序需要使用一块不存在的内存时(即在内存页表项中已标出相应内存页面不在内存中),通过80386的页错误异常中断来实现的.当一个进程引用一个不存在的页面中的内存地址时,就会触发CPU产生页出错异常中断,并把引起中断的线性地址放在CR2控制寄存器中.因此页中断处理程序就可以知道发生页异常的确切地址,从而可以把进程要求的页面从二级存储空间(比如硬盘上)加载到物理内存中.如果此时物理内存已经被全部占用,那么可以借助二级存储空间的一部分作为交换缓冲区(Swapper)把内存中暂时使用的页面交换到二级缓冲区中,然后把要求的页面调入内存中. 在mm/memory.c.
13.内存分页管理的基本原理. 将整个主内存区域分为4096字节为一页的内存页面.程序申请使用内存时,就以内存页为单位进行分配. 在GDT中的段描述符项数最大为256,其中2项空闲,2项系统使用,每个进程使用两项. 系统最多可以(256-4)/2+1=127个任务. 每个进程可以使用的最大虚拟地址范围是64M,所以,虚拟地址范围是(256-4)/2)*64MB=8G. 由于Intel80386最大寻址范围为4G.所以,人工定义的最大任务数=64,每个进程虚拟地址范围是64M,并且各个进程的虚拟地址起始位置是(任务号-1)*64MB. 所以可以使用的虚拟地址范围是64MB*64=4G.
14.每一个进程都包括:代码区,数据区,参数和堆栈区.
15.进程的虚拟地址需要首先通过其局部段描述符变换为CPU整个线性地址空间中的地址,然后再使用页目录表PDT(一级页表)和页表PT(二级页表)映身到实际物理地址页上.
16.Linux0.11使用了四种堆栈.
  A. 系统初始化临时使用的堆栈;
  B. 内核堆栈,供内核程序使用;
  C. 内核态堆栈,每一个进程通过系统调用,执行内核程序进使用的堆栈,每个进程都有自己独立的内核态堆栈.
  D. 用户态堆栈,每一个进程在用户态执行的堆栈,位于地址空间的末端.
17. 临时使用的堆栈.当bootsect被ROM BIOS引导加载到物理内存0x7c00处时,并没有设置堆栈段. 直到bootsect被移到0x9000:0处时,才把堆栈段寄存器SS设置为0x9000, 堆栈指针esp寄存器设置为0xff00, 即堆栈顶端在0x9000:0xff00处. 见boot/bootsect.S中.setup.S程序沿用此堆栈段.
18. 内核堆栈,堆栈段被设置为内核数据段(0x10),堆栈指针esp设置成指向user_stack数组的顶端,保留了1页内存(4K)作为堆栈使用. user_stack在数组在sched.c中定义,共含1024个长字.
19. 任务内核态堆栈与用户态堆栈之间的切换. 当进入内核程序时,由于优先级别发生了改变(从用户转到内核态),用户态堆栈的堆栈段和堆栈指针以及eflags会被保存在任务的内核态堆栈中.
20. 在执行iret退出内核程序返回到用户程序时,将恢复用户态的堆栈和eflags.
21. Linux源代码目录树.
linux
|
|---boot         系统引导汇编程序
|---fs           文件系统
|---include      头文件(*.h)
|   |----asm     与CPU体系结构相关的部分  
|   |----linux   Linux内核专用部分
|   |----sys     系统数据结构部分
|---init         内核初始化程序
|---kernel       内核进程调度,信号处理,系统调用等程序
|   |----blk_drv 块设备驱动程序
|   |----chr_drv 字符设备驱动程序
|   |----math    数据协处理器仿真处理程序
|---lib          内核库函数
|---mm           内存管理程序
|---tools        生成内核Image文件的工具程序
////////////////////////////////////////////////////////////////
1. 终端驱动程序用于控制终端设备,在终端设备和进程之间传输数据,并对传输的数据进行一定的处理。用户在键盘上键入的原始数据(Raw Data), 在通过终端程序处理后,被传送给一个接收进程; 而进程向终端发送的数据,在终端程序处理后,被显示在终端屏幕上或者
通过串行线路被发送到远程终端。
2. 终端的工作模式分成两种。
A.规范模式(canonical), 经过终端程序的数据将被进行变换处理,然后再送出。在这个过程中使用的处理函数一般称为行规则(line disciplien)模块。
B.非规范模式或原始(raw)模式,在这种模式下,行规则程序仅在终端与进程之间传送数据,而不对数据进行规范模式的变换处理。
3. 在终端驱动程序中,分为字符设备的直接驱动程序和与上层直接联系的接口程序。
进程序读/写
          |/   /|
   终端驱动程序上层接口
          |/   /|
        行规则程序
          |/   /|
       设备驱动程序
          |/   /|
         字符设备           
4. 终端基本数据结构
每个终端设备都对应有一个tty_struct数据结构,主要用来保存终端设备当前参数设置、所属的前台进程组ID和字符I/O缓冲队列等信息。该结构定义在 include/linux/tty.h.
struct tty_struct {
struct termios termios; /* 终端io属性和控制字符数据结构。*/
int pgrp; /* 所属进程组 */
int stopped; /* 停止标志 */
void (*write) (struct tty_struct *tty); /* tty 写函数指针 */
struct tty_queue read_q; /* tty 读队列 */
struct tty_queue write_q; /* tty 写队列 */
struct tty_queue secondary; /* tty 辅助队列(存放规范模式字符序列 */
};
终端所处理的数据被保存在tty_queue结构的字符缓冲队列中(称为字符表)。
struct tty_queue {
unsigned long data; /* 等待队列缓冲区中当前数据统计值,
       如果是串行终端,而存放串口端口地址 */
unsigned long head; /* 缓冲区中数据头指针 */
unsigned long tail; /* 缓冲区中数据尾指针 */
struct task_struct *proc_list; /* 等待本缓冲队列的进程列表 */
char buf[1024]; /* 队列的缓冲区 */
}
其中,读缓冲队列read_q用于临时存放从键盘或串行终端输入的原始(raw)字符序列; 写缓冲队列write_q用于存放写到控制台显示屏或
串行终端的数据; 根据ICANON标志,secondary队列用于存放从read_q中取出的经过行规则程序处理过的数据,称为(cooked)模式数据。
上层终端读函数tty_read()即用于读取secondary队列中的字符。
5. 读写数据的过程
在读入用户键入的数据时,中断处理汇编程序只负责把原始字符数据放入输入缓冲队列中,而由在中断处理过程中调用的C函数
(copy_to_cooked())来处理字符的变换工作。
当进程向一个终端写数据时,终端驱动程序就会调用行规则函数copy_to_cooked(),把用户缓冲区中的所有数据写到缓冲队列中,并将数据发送到终端上显示。     
过程为:在终端上按下一个键时,所引发的键盘中断处理过程会根据按下的键值扫描对应的字符,并放入读队列read_q中,并调用规范模式处理程序把read_q中的字符经过处理再放入辅助队列secondary中。与此同时,如果终端设备设置了回显标志(L_ECHO),则也把该字符放入写队列write_q中,并调用终端写函数把该字符显示在屏幕上。
6. termios结构
在include/termios.h中。
struct termios {
unsigned long c_iflag; /* input mode flags */
unsigned long c_oflag; /* output mode flags */
unsigned long c_cflag; /* control mode flags */
unsigned long c_lflag; /* local mode flags */
unsigned char c_line; /* line discipline */
unsigned char c_cc[NCCS]; /* control characters */
};
7. 规范模式
  输入字符被装配成行,进程以字符行的形式读取。当一行字符输入后,终端驱动程序会立刻返回。
8. 非规范模式
  终端程序不对字符进行处理,将它们当作普通字符处理。输入数据也没有行的概念。终端程序何时返回读进程是由MIN和TIME的值确定。这两个变量是c_cc[]数据组中的变量。通过修改他们即可改变在非规范模式下进程读字符的处理方式。
  MIN: 指明读操作最少需要读取的字符数;
  TIME: 指定等待读取字符的超时值(计量单位是1/10秒).
  1. MIN>0, TIME>0 : 读取一个字符,定时在接收到一个字符之后起作用。如果没有接收到字符,则被阻塞。
  2. MIN>0, TIME=0 : 在接收到一个字符时,读操作返回,否则无限阻塞。
  3. MIN=0, TIME>0 : 在接收到一个字符或超时时,读操作立刻返回。
  4. MIN=0, TIME=0 : 如果队列中有数据可以读取,则读操作读取需要的字符数。否则立刻返回0字符数。
1. 终端驱动程序用于控制终端设备,在终端设备和进程之间传输数据,并对传输的数据进行一定的处理。用户在键盘上键入的原始数据(Raw Data), 在通过终端程序处理后,被传送给一个接收进程; 而进程向终端发送的数据,在终端程序处理后,被显示在终端屏幕上或者
通过串行线路被发送到远程终端。
2. 终端的工作模式分成两种。
A.规范模式(canonical), 经过终端程序的数据将被进行变换处理,然后再送出。在这个过程中使用的处理函数一般称为行规则(line disciplien)模块。
B.非规范模式或原始(raw)模式,在这种模式下,行规则程序仅在终端与进程之间传送数据,而不对数据进行规范模式的变换处理。
3. 在终端驱动程序中,分为字符设备的直接驱动程序和与上层直接联系的接口程序。
进程序读/写
          |/   /|
   终端驱动程序上层接口
          |/   /|
        行规则程序
          |/   /|
       设备驱动程序
          |/   /|
         字符设备           
4. 终端基本数据结构
每个终端设备都对应有一个tty_struct数据结构,主要用来保存终端设备当前参数设置、所属的前台进程组ID和字符I/O缓冲队列等信息。该结构定义在 include/linux/tty.h.
struct tty_struct {
struct termios termios; /* 终端io属性和控制字符数据结构。*/
int pgrp; /* 所属进程组 */
int stopped; /* 停止标志 */
void (*write) (struct tty_struct *tty); /* tty 写函数指针 */
struct tty_queue read_q; /* tty 读队列 */
struct tty_queue write_q; /* tty 写队列 */
struct tty_queue secondary; /* tty 辅助队列(存放规范模式字符序列 */
};
终端所处理的数据被保存在tty_queue结构的字符缓冲队列中(称为字符表)。
struct tty_queue {
unsigned long data; /* 等待队列缓冲区中当前数据统计值,
       如果是串行终端,而存放串口端口地址 */
unsigned long head; /* 缓冲区中数据头指针 */
unsigned long tail; /* 缓冲区中数据尾指针 */
struct task_struct *proc_list; /* 等待本缓冲队列的进程列表 */
char buf[1024]; /* 队列的缓冲区 */
}
其中,读缓冲队列read_q用于临时存放从键盘或串行终端输入的原始(raw)字符序列; 写缓冲队列write_q用于存放写到控制台显示屏或
串行终端的数据; 根据ICANON标志,secondary队列用于存放从read_q中取出的经过行规则程序处理过的数据,称为(cooked)模式数据。
上层终端读函数tty_read()即用于读取secondary队列中的字符。
5. 读写数据的过程
在读入用户键入的数据时,中断处理汇编程序只负责把原始字符数据放入输入缓冲队列中,而由在中断处理过程中调用的C函数
(copy_to_cooked())来处理字符的变换工作。
当进程向一个终端写数据时,终端驱动程序就会调用行规则函数copy_to_cooked(),把用户缓冲区中的所有数据写到缓冲队列中,并将数据发送到终端上显示。     
过程为:在终端上按下一个键时,所引发的键盘中断处理过程会根据按下的键值扫描对应的字符,并放入读队列read_q中,并调用规范模式处理程序把read_q中的字符经过处理再放入辅助队列secondary中。与此同时,如果终端设备设置了回显标志(L_ECHO),则也把该字符放入写队列write_q中,并调用终端写函数把该字符显示在屏幕上。
6. termios结构
在include/termios.h中。
struct termios {
unsigned long c_iflag; /* input mode flags */
unsigned long c_oflag; /* output mode flags */
unsigned long c_cflag; /* control mode flags */
unsigned long c_lflag; /* local mode flags */
unsigned char c_line; /* line discipline */
unsigned char c_cc[NCCS]; /* control characters */
};
7. 规范模式
  输入字符被装配成行,进程以字符行的形式读取。当一行字符输入后,终端驱动程序会立刻返回。
8. 非规范模式
  终端程序不对字符进行处理,将它们当作普通字符处理。输入数据也没有行的概念。终端程序何时返回读进程是由MIN和TIME的值确定。这两个变量是c_cc[]数据组中的变量。通过修改他们即可改变在非规范模式下进程读字符的处理方式。
  MIN: 指明读操作最少需要读取的字符数;
  TIME: 指定等待读取字符的超时值(计量单位是1/10秒).
  1. MIN>0, TIME>0 : 读取一个字符,定时在接收到一个字符之后起作用。如果没有接收到字符,则被阻塞。
  2. MIN>0, TIME=0 : 在接收到一个字符时,读操作返回,否则无限阻塞。
  3. MIN=0, TIME>0 : 在接收到一个字符或超时时,读操作立刻返回。
  4. MIN=0, TIME=0 : 如果队列中有数据可以读取,则读操作读取需要的字符数。否则立刻返回0字符数。
///////////////////////////////////////////////////////////////


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/14053/showart_82164.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP