Linux 0.12 内核

5 篇文章 0 订阅

特权级

  1. CPL: CS段寄存器0 1位
    RPL: 代码中描述符0 1位
    DPL: 所访问段0 1位
  2. linux代码段非一致代码段。
  3. IDT仅能存放门描述符。
  4. GDT能存放代码段、数据段、系统段(TSS段,LDT段,门描述符)

signal.c

  1. send_sig 只有euid相等,且pid == 0的才能发送信号

shed.c

  1. 系统调用无嵌套
  2. 陷阱是可以嵌套的,中断不可嵌套
  3. 信号处理:系统调用、用户态时钟中断、用户态协处理器中断
  4. schedule():sleep等系统调用、系统调用结束后if count==0、用户态时钟中断、信号处理返回值1
  5. ljmp tss 进行任务切换,用户态寄存器数据保存在内核栈,内核态用户数据保存在tss
  6. 陷阱门:系统调用 键盘
    中断门:hd 时钟 串行终端

hd.c

  1. do_hd_request() 处理当前请求
  2. bad_rw_intr() 之后一般需要调用 do_hd_request()
  3. do_hd_request() hd_out() 作用相似
  4. 主设备号在插入队列时使用,副设备号在do_xx_request()中hd_out()前使用

软盘引导过程

  1. BIOS加载到内存,执行BIOS,进行某些系统检测。BIOS加载启动设备第一个扇区(bootsect.S)到内存0X7C00处,并跳转到0X7C00
  2. bootsect.S把自己移动到0X90000处

硬盘引导过程

  1. BIOS加载到内存,执行BIOS,进行某些系统检测。BIOS加载启动设备第一个扇区MBR到内存0X7C00处,并跳转到0X7C00
  2. MBR把自己移动到0X6000处,以腾出0X7C00
  3. 将活动分区的第一个扇区读入到0X7C00执行
  4. bootsect.S把自己移动到0X90000处

buffer_head.c

  1. count 为进程占用数, count = 0 表示空闲(可占用)
  2. dev block 定位
  3. uptodate dirt 内容有效性
  4. 只有文件系统需要block位图,文件系统建立在buffer_head之上

bitmap.c

  1. block与i_num概念类似
  2. bitmap中的函数用于文件系统
  3. new()改变位图的同时需要在内存中创建结构

inode.c

  1. iget() get_pipeinode()类似于bread()
  2. iput()类似于brelse()
  3. read_inode() write_inode()类似ll_rw_block()
  4. 表中的均为有效的,不需要uptodate
  5. 只有写到磁盘上的那部分被更改,才需要dirt,(super buffer类似)

super.c

  1. read_super()类似bread(),只在安装文件系统时调用。get_super()在访问超级快时调用
  2. put_super()类似brelse()
  3. super_block无count,所以dew表示是否空闲
  4. mount是把inode(dev,nr)进行变换,挂载点必须是目录
  5. 块设备不存储0号inode 0号数据块,但位图中存储0位,0位在read_super()中被初始化为1

根文件系统

  1. init()调用系统调用setup()来安装根文件系统
  2. sys_setup()调用rd_load()试图从启动软盘把根文件系统加载到ramdisk,如果加载成功则改变ROOT_DEV = 0X101
  3. sys_setup()调用mount_root() 安装根文件系统
  4. 1.makefile未指定ROOT_DEV,则在bootsect.S中通过判断磁盘参数来确定ROOT_DEV,208页,这种情况是一体启动盘(根文件扇区和boot在同一软盘)
    2. MAJOR(ROOT_DEV) == 2 && RAMDISK 。如为一体启动盘(根文件扇区和boot在同一软盘),则根文件系统移动到RAMDISK安装。如不是一体启动盘,则打印插入软盘并从独立软盘安装根文件系统。
    3. 如为一体启动盘(根文件扇区和boot在同一软盘),必须define RAMDISK
    4. 其他情况,则从独立盘安装
    5. 可安装的文件系统必须占据完整的dev

  5. 根文件系统的根目录存储在inode数据current->root

namei.c

  1. follow_link(dir,inode),dir inode均被释放,返回dir所link的inode
  2. find_entry(dir,res_dir)不释放dir,会完成mount节点的转换
  3. 查找目录方法:inode + 名称->inode
  4. 目录权限必须为可执行

pipe.c

  1. 系统调用被信号中断,指P684 row25
  2. read_pipe()可能会发生系统调用被信号中断。当pipe无数据且有非阻塞信号,系统调用被中断。
    1. 如果read != 0,则不重新执行系统调用,直接返回。
    2. 如果read == 0,则-ERESTARTASTS可能重新执行系统调用

open.c

  1. namei() open_namei() get_pipe_inode() 返回inode
  2. sys_open() sys_pipe() 除了需要获得inode,还需要创建file filp,最后返回文件描述符
  3. (file*) task_struct -> filp (file inode*) file (inode) inode

exec.c

  1. copy_string()参数from_kmem只表示src位于用户空间还是内核空间,dst都是内核空闲页(保存在page数组中),dst为128KB空间
  2. 用户程序 代码段 数据段 为一个段
  3. 代码段、数据段的访问通过GDT、LDT映射到线性空间。发送缺页异常时,缺页产生线性地址通过公式 (address - current->start_code) / BLOCK_SIZE 映射到可执行文件块,使用可执行文件inode current->executable和函数bread_page()加载可执行文件到内存中,put_page()映射到线性空间
  4. 库起始60MB,终止64MB

用户程序 空间映射(逻辑 线性 物理 可执行文件)

  1. 可执行文件中代码段 数据段 为一个段。LDT中使用两项表示,用户代码段只读,用户数据段读写,两段base limit均一致
  2. 映射准备工作是初始化task_struct中的数据,如start_code(线性空间起始地址) end_data(数据代码统一段长度) current->executable current->library
  3. 映射:代码段、数据段的访问通过GDT、LDT映射到线性空间。发送缺页异常时,缺页产生线性地址通过公式 (address - current->start_code) / BLOCK_SIZE 映射到可执行文件块,使用可执行文件inode current->executable和函数bread_page()加载可执行文件到内存中,put_page()映射到线性空间
  4. 逻辑空间->线性空间->可执行文件的映射是初始化task_struct后已经确定的
  5. 64MB用户空间为,从高到低 lib -> stack -> heap -> bss -> data -> code

bochs

  1. 17.4是在LINUX0.12系统中编译LINUX0.0,把编译结果文件安装到LINUX0.12的启动盘中,然后重启。image只是编译结果文件,不是模拟盘
  2. gdb调试,调试执行文件的编译环境必须一致

keyboard.S

  1. mode表记录按下状态,leds表记录按下次数状态

console.c

  1. 行数 列数 与 每行使用字节数 每列使用字节数 不一样,每行使用字节数video_size_row = video_num_columns * 2;,每列使用字节数 = 2
  2. x y 是光标在当前屏幕的行列,pos是光标在内存中的地址
  3. srcup() srcdown()不会改变x y,即不改变光标位置
  4. console.c主要作用是用键盘和显示器模拟终端。80行后的全局变量是显示器硬件参数(显示器参数),为所有虚拟终端共享。每个虚拟终端还有自身的参数,存储在vc_cons。

中断

  1. 初始化:
    1. set_xx_gate()更新中断表
    2. 初始化硬件控制器
    3. outb()更改屏蔽码,允许中断请求
  2. 每次处理完中断后,需发送EOI信号
  3. 对于键盘,每次处理完中断后,需对键盘硬件电路进行复位,再发生EOI

serial.c

  1. rs_init()类似con_init()
  2. rs_write()类似con_write()

rs_io.s

  1. rs1_interrupt类似keyboard_interrupt,且都是缓冲区已满时丢弃

tty_io.c

  1. 8个虚拟终端在tty_table第0到第7项,tty1为第0项,tty8为第7项。fg_console为前台控制台号
  2. 控制字符和控制序列不显示,仅用作控制文本字符的显示、处理和传送。控制字符可以通过crtl + c(0x40 <= c <= 0x5f)从键盘输入。copy_to_cooked()处理控制字符,con_write()处理控制序列
  3. 已经输送到write_q的数据不能撤回,只能再次输入127(DEL 退格符)。注意DEL与空格符不同。
  4. tty_read()中current->timeout为设置的某一时刻唤醒进程并返回(当辅助队列中没有读取条件时进程会interruptible)。
  5. tty_read()从secondary队列读取。
    1. 规范模式下。一行字符数 > nr时,读取nr。一行字符数 <= nr时,读取一行字符(c == 10 读取,EOF 不读取但丢弃)
    2. 非规范模式下,根据VMIN VTIME分为4种情况
  6. tty_read() tty_write() 如果条件不满足都需要休眠等待
    1. tty_read()可被copy_to_cooked()唤醒,可被current->timeout唤醒,可被信号唤醒(立即返回,如已读入为0则重新执行系统调用)
    2. tty_write()可被rs_write()唤醒,应该也可被con_write()唤醒但0.12没有该代码,可被信号唤醒(立即返回)
      7.疑问,keyboard中断set_trap_gate()
int tty_read(unsigned channel, char * buf, int nr)
{
	struct tty_struct * tty;
	struct tty_struct * other_tty = NULL;
	char c, * b=buf;
	int minimum,time;

	if (channel > 255)
		return -EIO;
	tty = TTY_TABLE(channel);
	if (!(tty->write_q || tty->read_q || tty->secondary))
		return -EIO;
	if ((current->tty == channel) && (tty->pgrp != current->pgrp)) 
		return(tty_signal(SIGTTIN, tty));
	if (channel & 0x80)
		other_tty = tty_table + (channel ^ 0x40);
	time = 10L*tty->termios.c_cc[VTIME];
	minimum = tty->termios.c_cc[VMIN];
	if (L_CANON(tty)) {							//规范
		minimum = nr;
		current->timeout = 0xffffffff;
		time = 0;
	} else if (minimum)
		current->timeout = 0xffffffff;			//min > 0
	else {										//min == 0
		minimum = nr;					
		if (time)								//min == 0 && time > 0
			current->timeout = time + jiffies;
		time = 0;
	}
	if (minimum>nr)
		minimum = nr;
	while (nr>0) {
		if (other_tty)
			other_tty->write(other_tty);
		cli();
		if (EMPTY(tty->secondary) || (L_CANON(tty) &&
		    !FULL(tty->read_q) && !tty->secondary->data)) {			//这个条件是当前辅助队列没有条件
			if (!current->timeout ||										
			  (current->signal & ~current->blocked)) {				//current->timeout 表示 time == 0 && min == 0 或刚被唤醒,直接返回
			  	sti();												//有信号返回,如读取数为0则重新执行系统调用
				break;
			}
			if (IS_A_PTY_SLAVE(channel) && C_HUP(other_tty))		
				break;
			interruptible_sleep_on(&tty->secondary->proc_list);		//睡眠到有信号或current->timeout
			sti();
			continue;
		}
		sti();
		do {				//读一行或为空
			GETCH(tty->secondary,c);
			if ((EOF_CHAR(tty) != _POSIX_VDISABLE &&
			     c==EOF_CHAR(tty)) || c==10)
				tty->secondary->data--;
			if ((EOF_CHAR(tty) != _POSIX_VDISABLE &&		//如果规范,且为EOF,则直接退出不输出
			     c==EOF_CHAR(tty)) && L_CANON(tty))
				break;
			else {
				put_fs_byte(c,b++);
				if (!--nr)
					break;
			}
			if (c==10 && L_CANON(tty))				//如果规范,且为换行,则输出并退出
				break;
		} while (nr>0 && !EMPTY(tty->secondary));	//为空退出
		wake_up(&tty->read_q->proc_list);
		if (time)									//min > 0 && time > 0
			current->timeout = time+jiffies;
		if (L_CANON(tty) || b-buf >= minimum)
			break;
	}
	current->timeout = 0;
	if ((current->signal & ~current->blocked) && !(b-buf))			//信号唤醒才重新执行系统调用
		return -ERESTARTSYS;
	return (b-buf);
}

Linux缓冲区与\n

  1. printf() 遇到\n输出,是因为标准库的缓冲区,例子如下:
    此函数就不会立马打印hello,而是等待5秒后出现helloworld,这就是输出缓冲区在作用。
    当printf读进字符串的时候,并不会马上打印,而是先储存在buff里面,当遇到下面4种条件的时候才进行打印:
    1、程序结束 return 0; 或 exit(0);
    2、遇到”\n”
    3、主动刷新 fflush(stdout);
    4、缓冲区满
    所以,hello只是被存储在了缓冲区里,没有遇到上面条件的一种,这就是为什么会先等待,后出现helloworld。
    当在第一个printf里加入”\n”,之后就会先打印hello,之后等待5秒,换行继续打印world
    ps:为什么要引入缓冲区,是因为进行用户态和内核态的切换过于麻烦,引入缓冲区可以使系统更加有效率的运行,具体内容可以参考系统调用与文件操作。
    原文链接
int main()
{
       printf(“hello”);
       sleep(5);
       printf(“world\n”);
 
       exit(0);
}
  1. 缓冲区的提供者
    首先介绍一下几个概念
    (1)系统调用:操作系统直接暴露给用户的接口。
    (2)库函数:把系统调用进行二次封装之后给用户使用的结果叫做库函数(封装系统调用,实现相同的复杂逻辑,节省工作量)
    (3)因此库函数和系统调用具有层级关系,库函数是系统调用的上层(以f开头的函数如fwrite都是库函数,去掉f如write是系统调用)
    (4)系统调用不具有缓冲区,库函数具有缓冲区,而库函数是系统调用的上层,因此,缓冲区是C语言库提供的。具体一点是由库中的文件操作符提贡的
    原文链接
  2. scanf()遇到\n才读入,因为终端规范模式,tty_read()读secondary时需要读取一整行,如果secondary->data(表示行数)==0则睡眠等待

终端

  1. 终端是类似rs1 rs2的设备,即串行终端,pc使用显示器和键盘模拟了几个虚拟终端(即控制台),分别是tty1 tty2 tty3 …,tty0表示当前使用的虚拟终端(fg_console或前台控制台)
  2. console.c主要作用是用键盘和显示器模拟终端。80行后的全局变量是显示器硬件参数(显示器参数),为所有虚拟终端共享。每个虚拟终端还有自身的参数,存储在vc_cons。
  3. 控制终端为current->tty,进程0进程1没有控制终端,控制终端绑定发生在main.c 218行
    1. setsid()创建新session,并把会话首进程(shell进程)current->leader置位。
    2. current->leader == 1时,open("/dev/tty1")会调用check_char_dev()绑定tty1为会话控制终端
    3. 绑定后,本session中所有进程控制终端tty均为tty1,tty1结构session = pgrp = sid。前台进程组可以向控制终端输入输出
    4. 解绑发生在shell进程exit()
  4. 控制终端主设备号为5,在rw_tty()中转换为某一终端。一般终端设备主设备号为4
  5. 0.11 中 tty1设备号为0,.12 中 tty1设备号为1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值