文章目录
特权级
- CPL: CS段寄存器0 1位
RPL: 代码中描述符0 1位
DPL: 所访问段0 1位 - linux代码段非一致代码段。
- IDT仅能存放门描述符。
- GDT能存放代码段、数据段、系统段(TSS段,LDT段,门描述符)
signal.c
- send_sig 只有euid相等,且pid == 0的才能发送信号
shed.c
- 系统调用无嵌套
- 陷阱是可以嵌套的,中断不可嵌套
- 信号处理:系统调用、用户态时钟中断、用户态协处理器中断
- schedule():sleep等系统调用、系统调用结束后if count==0、用户态时钟中断、信号处理返回值1
- ljmp tss 进行任务切换,用户态寄存器数据保存在内核栈,内核态用户数据保存在tss
- 陷阱门:系统调用 键盘
中断门:hd 时钟 串行终端
hd.c
- do_hd_request() 处理当前请求
- bad_rw_intr() 之后一般需要调用 do_hd_request()
- do_hd_request() hd_out() 作用相似
- 主设备号在插入队列时使用,副设备号在do_xx_request()中hd_out()前使用
软盘引导过程
- BIOS加载到内存,执行BIOS,进行某些系统检测。BIOS加载启动设备第一个扇区(bootsect.S)到内存0X7C00处,并跳转到0X7C00
- bootsect.S把自己移动到0X90000处
硬盘引导过程
- BIOS加载到内存,执行BIOS,进行某些系统检测。BIOS加载启动设备第一个扇区MBR到内存0X7C00处,并跳转到0X7C00
- MBR把自己移动到0X6000处,以腾出0X7C00
- 将活动分区的第一个扇区读入到0X7C00执行
- bootsect.S把自己移动到0X90000处
buffer_head.c
- count 为进程占用数, count = 0 表示空闲(可占用)
- dev block 定位
- uptodate dirt 内容有效性
- 只有文件系统需要block位图,文件系统建立在buffer_head之上
bitmap.c
- block与i_num概念类似
- bitmap中的函数用于文件系统
- new()改变位图的同时需要在内存中创建结构
inode.c
- iget() get_pipeinode()类似于bread()
- iput()类似于brelse()
- read_inode() write_inode()类似ll_rw_block()
- 表中的均为有效的,不需要uptodate
- 只有写到磁盘上的那部分被更改,才需要dirt,(super buffer类似)
super.c
- read_super()类似bread(),只在安装文件系统时调用。get_super()在访问超级快时调用
- put_super()类似brelse()
- super_block无count,所以dew表示是否空闲
- mount是把inode(dev,nr)进行变换,挂载点必须是目录
- 块设备不存储0号inode 0号数据块,但位图中存储0位,0位在read_super()中被初始化为1
根文件系统
- init()调用系统调用setup()来安装根文件系统
- sys_setup()调用rd_load()试图从启动软盘把根文件系统加载到ramdisk,如果加载成功则改变ROOT_DEV = 0X101
- sys_setup()调用mount_root() 安装根文件系统
-
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 - 根文件系统的根目录存储在inode数据current->root
namei.c
- follow_link(dir,inode),dir inode均被释放,返回dir所link的inode
- find_entry(dir,res_dir)不释放dir,会完成mount节点的转换
- 查找目录方法:inode + 名称->inode
- 目录权限必须为可执行
pipe.c
- 系统调用被信号中断,指P684 row25
- read_pipe()可能会发生系统调用被信号中断。当pipe无数据且有非阻塞信号,系统调用被中断。
- 如果read != 0,则不重新执行系统调用,直接返回。
- 如果read == 0,则-ERESTARTASTS可能重新执行系统调用
open.c
- namei() open_namei() get_pipe_inode() 返回inode
- sys_open() sys_pipe() 除了需要获得inode,还需要创建file filp,最后返回文件描述符
- (file*) task_struct -> filp (file inode*) file (inode) inode
exec.c
- copy_string()参数from_kmem只表示src位于用户空间还是内核空间,dst都是内核空闲页(保存在page数组中),dst为128KB空间
- 用户程序 代码段 数据段 为一个段
- 代码段、数据段的访问通过GDT、LDT映射到线性空间。发送缺页异常时,缺页产生线性地址通过公式 (address - current->start_code) / BLOCK_SIZE 映射到可执行文件块,使用可执行文件inode current->executable和函数bread_page()加载可执行文件到内存中,put_page()映射到线性空间
- 库起始60MB,终止64MB
用户程序 空间映射(逻辑 线性 物理 可执行文件)
- 可执行文件中代码段 数据段 为一个段。LDT中使用两项表示,用户代码段只读,用户数据段读写,两段base limit均一致
- 映射准备工作是初始化task_struct中的数据,如start_code(线性空间起始地址) end_data(数据代码统一段长度) current->executable current->library
- 映射:代码段、数据段的访问通过GDT、LDT映射到线性空间。发送缺页异常时,缺页产生线性地址通过公式 (address - current->start_code) / BLOCK_SIZE 映射到可执行文件块,使用可执行文件inode current->executable和函数bread_page()加载可执行文件到内存中,put_page()映射到线性空间
- 逻辑空间->线性空间->可执行文件的映射是初始化task_struct后已经确定的
- 64MB用户空间为,从高到低 lib -> stack -> heap -> bss -> data -> code
bochs
- 17.4是在LINUX0.12系统中编译LINUX0.0,把编译结果文件安装到LINUX0.12的启动盘中,然后重启。image只是编译结果文件,不是模拟盘
- gdb调试,调试执行文件的编译环境必须一致
keyboard.S
- mode表记录按下状态,leds表记录按下次数状态
console.c
- 行数 列数 与 每行使用字节数 每列使用字节数 不一样,每行使用字节数video_size_row = video_num_columns * 2;,每列使用字节数 = 2
- x y 是光标在当前屏幕的行列,pos是光标在内存中的地址
- srcup() srcdown()不会改变x y,即不改变光标位置
- console.c主要作用是用键盘和显示器模拟终端。80行后的全局变量是显示器硬件参数(显示器参数),为所有虚拟终端共享。每个虚拟终端还有自身的参数,存储在vc_cons。
中断
- 初始化:
- set_xx_gate()更新中断表
- 初始化硬件控制器
- outb()更改屏蔽码,允许中断请求
- 每次处理完中断后,需发送EOI信号
- 对于键盘,每次处理完中断后,需对键盘硬件电路进行复位,再发生EOI
serial.c
- rs_init()类似con_init()
- rs_write()类似con_write()
rs_io.s
- rs1_interrupt类似keyboard_interrupt,且都是缓冲区已满时丢弃
tty_io.c
- 8个虚拟终端在tty_table第0到第7项,tty1为第0项,tty8为第7项。fg_console为前台控制台号
- 控制字符和控制序列不显示,仅用作控制文本字符的显示、处理和传送。控制字符可以通过crtl + c(0x40 <= c <= 0x5f)从键盘输入。copy_to_cooked()处理控制字符,con_write()处理控制序列
- 已经输送到write_q的数据不能撤回,只能再次输入127(DEL 退格符)。注意DEL与空格符不同。
- tty_read()中current->timeout为设置的某一时刻唤醒进程并返回(当辅助队列中没有读取条件时进程会interruptible)。
- tty_read()从secondary队列读取。
- 规范模式下。一行字符数 > nr时,读取nr。一行字符数 <= nr时,读取一行字符(c == 10 读取,EOF 不读取但丢弃)
- 非规范模式下,根据VMIN VTIME分为4种情况
- tty_read() tty_write() 如果条件不满足都需要休眠等待
- tty_read()可被copy_to_cooked()唤醒,可被current->timeout唤醒,可被信号唤醒(立即返回,如已读入为0则重新执行系统调用)
- 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
- 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)系统调用:操作系统直接暴露给用户的接口。
(2)库函数:把系统调用进行二次封装之后给用户使用的结果叫做库函数(封装系统调用,实现相同的复杂逻辑,节省工作量)
(3)因此库函数和系统调用具有层级关系,库函数是系统调用的上层(以f开头的函数如fwrite都是库函数,去掉f如write是系统调用)
(4)系统调用不具有缓冲区,库函数具有缓冲区,而库函数是系统调用的上层,因此,缓冲区是C语言库提供的。具体一点是由库中的文件操作符提贡的
原文链接 - scanf()遇到\n才读入,因为终端规范模式,tty_read()读secondary时需要读取一整行,如果secondary->data(表示行数)==0则睡眠等待
终端
- 终端是类似rs1 rs2的设备,即串行终端,pc使用显示器和键盘模拟了几个虚拟终端(即控制台),分别是tty1 tty2 tty3 …,tty0表示当前使用的虚拟终端(fg_console或前台控制台)
- console.c主要作用是用键盘和显示器模拟终端。80行后的全局变量是显示器硬件参数(显示器参数),为所有虚拟终端共享。每个虚拟终端还有自身的参数,存储在vc_cons。
- 控制终端为current->tty,进程0进程1没有控制终端,控制终端绑定发生在main.c 218行
- setsid()创建新session,并把会话首进程(shell进程)current->leader置位。
- current->leader == 1时,open("/dev/tty1")会调用check_char_dev()绑定tty1为会话控制终端
- 绑定后,本session中所有进程控制终端tty均为tty1,tty1结构session = pgrp = sid。前台进程组可以向控制终端输入输出
- 解绑发生在shell进程exit()
- 控制终端主设备号为5,在rw_tty()中转换为某一终端。一般终端设备主设备号为4
- 0.11 中 tty1设备号为0,.12 中 tty1设备号为1