首页 网站首页 商业资讯 操作 查看内容

操作系统实验一到实验九合集(哈工大李治军)

私域营销网 操作 2022-11-24 21:20 8694人围观

知乎Markdown适配不可,希望在我的博客中检察文章

作者寄语

操纵系统尝试的进修是一个循序渐进的进程,初度看linux-0.11中的代码,看着满屏的汇编说话,确切头疼。但经过进修赵炯博士的Linux内核0.11完全正文,连系着王爽教员的汇编说话一书,我逐步了解每段汇编说话的寄义和感化。本文主如果经过对哈工大李治军配套尝试的实现,侧重诠释每一段的汇编代码,使读者对尝试的整体头绪有一个初步的熟悉,不再由于害怕汇编而不放弃尝试。本文只是举一反三,希望读者可以深入研讨我下文供给的参考材料,做到理论与理论兼具。

参考材料

  • 视频:操纵系统
  • 书籍:现代操纵系统
  • 尝试:操纵系统道理与理论
  • 书籍:霸道操纵系统
  • 书籍:Linux内核完全正文
  • 书籍:汇编说话(第3版) 王爽著

目录

[TOC]

尝试一 熟悉尝试情况

只是熟悉尝试情况,我没有益用蓝桥云课的尝试情况,而是经过阿里云办事器搭建了linux情况,有需要的可以看以下2篇文章

  • 阿里云办事器Ubuntu14.04(64位)安装图形化界面_leoabcd12的博客-CSDN博客
  • 阿里云ubuntu系统设置linux-0.11(哈工大 李治军)尝试情况搭建_leoabcd12的博客-CSDN博客

尝试二 操纵系统的指导

Linux 0.11 文件夹中的 boot/bootsect.sboot/setup.stools/build.c 是本尝试会触及到的源文件。它们的功用详见《Linux内核0.11完全正文》的 6.2、6.3 节和 16 章。

汇编常识

简要整理了一下此次尝试所需的根本汇编常识,可以鄙人文阅读代码是碰到再回过甚来看!

int 0x10





image-20210808151038949

留意,这里ah要先有值,代表内部子法式的编号

功用号 ah=0x03​​​,感化是读取光标的位置

  • 输入:bh = 页号
  • 返回:ch = 扫描起头线;cl = 扫描竣事线;dh = 行号;dl = 列号

功用号 ah=0x13,感化是显现字符串

  • 输入:al = 放置光标的方式及规定属性,下文 al=1,暗示方针字符串仅仅包括字符,属性在BL中包括,光标停在字符串结尾处;es:bp = 字符串肇端位置;cx = 显现的字符串字符数;bh = 页号;bl = 字符属性,下文 bl = 07H,暗示一般的黑底白字;dh = 行号;dl = 列号

功用号 ah=0x0e​,感化是显现字符

  • 输入:al = 字符

int 0x13

在DOS等实形式操纵系统下,挪用INT 13h会跳转到计较机的ROM-BIOS代码中停止低级磁盘办事,对法式停止基于物理扇区的磁盘读写操纵。

功用号 ah=0x02,感化是读磁盘扇区到内存

  • 输入:
寄存器寄义
ah读磁盘扇区到内存
al需要读出的扇区数目
ch磁道
cl扇区
dh磁头
dl驱动器
es:bx数据缓冲区的地址

  • 返回:ah = 出错码(00H暗示无错,01H暗示不法号令,02H暗示地址方针未发现...);CF为进位标志位,假如没有出错 CF=0​

功用号 ah=0x00​,感化是磁盘系统复位

  • 输入:dl = 驱动器
  • 返回:假如操纵成功———— CF=0​​,ah=00H​​

这里我只挑了下文需要的先容,更多内容可以参考这篇博客BIOS系统办事 —— 间接磁盘办事(int 0x13)

int 0x15

功用号 ah=0x88,感化是获得系统所含扩大内存巨细

  • 输入:ah = 0x88
  • 返回:ax = 从0x100000(1M)处起头的拓展内存巨细(KB)。若出错则CF置位,ax = 出错码。

int 0x41

在PC机中BIOS设定的中断向量表中int 0x41的中断向量位置 (4*0x41 = 0x0000:0x0104​​​)寄存的并不是中断法式的地址,而是第一个硬盘的根基参数表。对于100%兼容的BIOS来说,这里寄存着硬盘参数表阵列的首地址0xF000:0E401,第二个硬盘的根基参数表进口地址存于int 0x46中断向量位置处.每个硬盘参数表有16个字节巨细.

位移巨细说明
0x00柱面数
0x02字节磁头数
.........
0x0E字节每磁道扇区数
0x0F字节保存

CF

方法会CF,首先要晓得寄存器中有一种特别的寄存器————标志寄存器,其中存储的信息凡是被称为法式状态字。以下简称为flag寄存器。

flag和其他寄存器纷歧样,其他寄存器是用来寄存数据的,都是全部寄存用具有一个寄义。而flag寄存器是按位起感化的,也就是说,它的每一位都有专门的寄义,记录特定的信息。




image-20210810220308173

flag的1、3、5、12、13、14、15位在8086CPU中没有益用,不具有任何寄义。而0、2、4、6、7、8、9、10、11位都具有特别的寄义。

CF就是flag的第0位————进位标志位。在停止==无标记数==运算的时辰,它记录了运算成果的最高有用位向更高位的==进位值==,或从更高位的借位值。

jnc

在 CF=0​​​ 的时辰,停止跳转,即不进位则跳转,下文就是在读入没有出错时,跳转到ok_load_setup

jl

小于则跳转

lds

格式: LDS reg16,mem32

其意义是同时给一个段寄存器和一个16位通用寄存器同时赋值

举例:

地址100H101H102H103H
内容00H41H02H03H
LDS AX,[100H]! 成果:AX=4100H DS=0302H

可以把上述代码了解为这样一个进程,但现实上不能这么写

mov AX,[100H]mov DS,[100H+2]

即把低字(2B)置为偏移地址,高字(2B)置为段地址

DF标志和串传送指令

flag的第10位是DF,偏向标志位。在串处置指令中,控制每次操纵后si、di的增减。

  • df=0:每次操纵后si、di递增
  • df=1:每次操纵后si、di递加

来看一个串传送指令

  • 格式:movsb
  • 功用:相当于履行了以下2步操纵
  1. ((es)*16+(di))=((ds)*16+si)
  2. 假如df=0:(si)=(si)+1,(di)=(di)+1
    假如df=1:(si)=(si)-1,(di)=(di)-1

可以看出,movsb的功用是将 ds:si 指向的内存单元中的字节送入 es:di中,然后按照标志寄存器df位的值,将si和di递增或递加。

也可以传送一个字

  • 格式:movsw
  • 功用:相当于履行了以下2步操纵
  1. ((es)*16+(di))=((ds)*16+si)
  2. 假如df=0:(si)=(si)+2,(di)=(di)+2
    假如df=1:(si)=(si)-2,(di)=(di)-2

可以看出,movsw的功用是将 ds:si 指向的内存单元中的字节送入 es:di​中,然后按照标志寄存器df位的值, 将si和di递增2或递加2。

movsb和movsw停止的是串传送操纵的一个步调,一般配合rep利用

格式以下:rep movsb

用汇编语法描写:

s:movsb loop s

可见rep的感化是按照cx的值,反复履行串传送指令。由于每履行一次movsb指令si和di城市递增或递加指向前面一个单元或前面一个单元,则 rep movsb便可以循环实现(cx)个字符的传送。

call

(1) 将当前IP或CS和IP压入栈中

(2) 转移

CPU履行“call 标号”时,相当于停止:

push IP
jmp near ptr 标号

ret

ret指令用栈中的数据,点窜IP的内容,从而实现近转移

(1) (IP)=((ss)*16+(sp))

(2) (sp)=(sp)+2​

CPU履行ret指令时,相当于停止:

pop IP

改写bootsect.s

翻开 bootsect.s




image-20210711135216130



image-20210814222810462

Loading system ...就是开机时显现在屏幕上的字,共16字符,加上3个换行+回车,一共是24字符。我将要点窜他为 Hello OS world, my name is WCF,30字符,加上3个换行+回车,共36字符。所以图一代码点窜成mov cx.#36

.org 508 点窜成 .org 510,是由于这里不需要 root_dev: .word ROOT_DEV,为了保证 boot_flag 一定在指导扇区最初两个字节,所以要点窜 .org.org 510 暗示下面语句从地址510(0x1FE)起头,用来强迫要求boot_flag一定在指导扇区的最初2个字节中(第511和512字节)。

完整的代码以下:

entry _start
_start:
mov ah,#0x03 ! 设备功用号
xor bh,bh ! 将bh置0
int 0x10 ! 返回行号和列号,供显现串用
mov cx,#52 !要显现的字符串长度
mov bx,#0x0007 ! bh=0,bl=07(一般的黑底白字)
mov bp,#msg1 ! es:bp 要显现的字符串物理地址
mov ax,#0x07c0 ! 将es段寄存器置为#0x07c0
mov es,ax
mov ax,#0x1301 ! ah=13(设备功用号),al=01(方针字符串仅仅包括字符,属性在BL中包括,光标停在字符串结尾处)
int 0x10 ! 显现字符串

! 设备一个无穷循环(纯洁为了能一向看到字符串显现)
inf_loop:
jmp inf_loop

! 字符串信息
msg1:
.byte 13,10 ! 换行+回车
.ascii "Welcome to the world without assembly language"
.byte 13,10,13,10 ! 换行+回车

! 将
.org 510

! 启动盘具有有用指导扇区的标志。仅供BIOS中的法式加载指导扇区时识别利用。它必须位于指导扇区的最初两个字节中
boot_flag:
.word 0xAA55

Ubuntu 上先从终端进入 ~/oslab/linux-0.11/boot/目录

履行下面两个号令编译和链接 bootsect.s

$ as86 -0 -a -o bootsect.o bootsect.s
$ ld86 -0 -s -o bootsect bootsect.o




image-20210711112503976

其中 bootsect.o 是中心文件。bootsect 是编译、链接后的方针文件。

需要留意的文件是 bootsect 的文件巨细是 544 字节,而指导法式必必要恰好占用一个磁盘扇区,即 512 个字节。形成多了 32 个字节的缘由是 ld86 发生的是 Minix 可履行文件格式,这样的可履行文件处置文本段、数据段等部分之外,还包括一个 Minix 可履行文件头部,它的结构以下:

struct exec {
unsigned char a_magic[2]; //履行文件魔数
unsigned char a_flags;
unsigned char a_cpu; //CPU标识号
unsigned char a_hdrlen; //头部长度,32字节或48字节
unsigned char a_unused;
unsigned short a_version;
long a_text; long a_data; long a_bss; //代码段长度、数据段长度、堆长度
long a_entry; //履行进口地址
long a_total; //分派的内存总量
long a_syms; //标记表巨细
};

6 char(6 字节)+ 1 short(2 字节) + 6 long(24 字节)= 32,恰好是 32 个字节,去掉这 32 个字节后便可以放入指导扇区了。

对于上面的 Minix 可履行文件,其 a_magic[0]=0x01,a_magic[1]=0x03,a_flags=0x10(可履行文件),a_cpu=0x04(暗示 Intel i8086/8088,假如是 0x17 则暗示 Sun 公司的 SPARC),所以 bootsect 文件的头几个字节应当是 01 03 10 04。为了考证一下,Ubuntu 下用号令hexdump -C bootsect可以看到:




image-20210814222824190

去掉这 32 个字节的文件头部

$ dd bs=1 if=bootsect of=Image skip=32

天生的 Image 就是去掉文件头的 bootsect

去掉这 32 个字节后,将天生的文件拷贝到 linux-0.11 目录下,并一定要命名为“Image”(留意巨细写)。然后就“run”吧!

# 当前的工作途径为 /oslab/linux-0.11/boot/
# 将刚刚天生的 Image 复制到 linux-0.11 目录下
$ cp ./Image ../Image
# 履行 oslab 目录中的 run 剧本
$ ../../run




image-20210814222834302

bootsect.s读入setup.s

首先编写一个 setup.s,该 setup.s 可以就间接拷贝前面的 bootsect.s(还需要简单的调剂),然后将其中的显现的信息改成:“Now we are in SETUP”。

和前面根基一样,就不正文了。

entry _start
_start:
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#25
mov bx,#0x0007
mov bp,#msg2
mov ax,cs ! 这里的cs实在就是这段代码的段地址
mov es,ax
mov ax,#0x1301
int 0x10
inf_loop:
jmp inf_loop
msg2:
.byte 13,10
.ascii "Now we are in SETUP"
.byte 13,10,13,10
.org 510
boot_flag:
.word 0xAA55

接下来需要编写 bootsect.s 中载入 setup.s 的关键代码

一切需要的功用在原版 bootsect.s 中都是存在的,我们要做的仅仅是将这些代码增加到新的 bootsect.s 中去。

除了新增代码,我们还需要去掉在 bootsect.s 增加的无穷循环。

SETUOLEN=2 ! 读入的扇区数
SETUPSEG=0x07e0 ! setup代码的段地址
entry _start
_start:
mov ah,#0x03 ! 设备功用号
xor bh,bh ! 将bh置0
int 0x10 ! 返回行号和列号,供显现串用
mov cx,#52 !要显现的字符串长度
mov bx,#0x0007 ! bh=0,bl=07(一般的黑底白字)
mov bp,#msg1 ! es:bp 要显现的字符串物理地址
mov ax,#0x07c0 ! 将es段寄存器置为#0x07c0
mov es,ax
mov ax,#0x1301 ! ah=13(设备功用号),al=01(方针字符串仅仅包括字符,属性在BL中包括,光标停在字符串结尾处)
int 0x10 ! 显现字符串

! 将setup模块从磁盘的第二个扇区起头读到0x7e00
load_setup:
mov dx,#0x0000 ! 磁头=0;驱动器号=0
mov cx,#0x0002 ! 磁道=0;扇区=2
mov bx,#0x0200 ! 偏移地址
mov ax,#0x0200+SETUPLEN ! 设备功用号;需要读出的扇区数目
int 0x13 ! 读磁盘扇区到内存
jnc ok_load_setup ! CF=0(读入成功)跳转到ok_load_setup
mov dx,#0x0000 ! 假如读入失利,利勤奋用号ah=0x00————磁盘系统复位
mov ax,#0x0000
int 0x13
jmp load_setup ! 尝试重新读入

ok_load_setup:
jmpi 0,SETUPSEG ! 段间跳转指令,跳转到setup模块处(0x07e0:0000)

! 字符串信息
msg1:
.byte 13,10 ! 换行+回车
.ascii "Welcome to the world without assembly language"
.byte 13,10,13,10 ! 换行+回车

! 将
.org 510

! 启动盘具有有用指导扇区的标志。仅供BIOS中的法式加载指导扇区时识别利用。它必须位于指导扇区的最初两个字节中
boot_flag:
.word 0xAA55

再次编译

$ make BootImage

有 Error!这是由于 make 按照 Makefile 的指引履行了 tools/build.c,它是为天生全部内核的镜像文件而设想的,没斟酌我们只需要 bootsect.ssetup.s 的情况。它在向我们要 “系统” 的焦点代码。为完成尝试,接下来给它打个小补钉。c

build.c 从号令行参数获得 bootsect、setup 和 system 内核的文件名,将三者做简单的整理后一路写入 Image。其中 system 是第三个参数(argv[3])。当 “make all” 大概 “makeall” 的时辰,这个参数传过来的是正确的文件名,build.c 会翻开它,将内容写入 Image。而 “make BootImage” 时,传过来的是字符串 "none"。所以,革新 build.c 的思绪就是当 argv[3] 是"none"的时辰,只写 bootsect 和 setup,疏忽一切与 system 有关的工作,大概在该写 system 的位置都写上 “0”。

点窜工作首要集合在 build.c 的尾部,可长度以参考下面的方式,将圈起来的部分正文掉。




image-20210814222844903

重新编译

$ cd ~/oslab/linux-0.11
$ make BootImage
$ ../run




image-20210814222851074

setup.s获得根基硬件参数

这里把一些难以了解的代码零丁列出来

  1. 获得磁盘参数

这里花了我很长时候,缘由是概念没有搞清楚,我感觉教员在尝试指导书上写的也不是很清楚,CSDN上都只是草草复制的代码,感受他们可以压根没有了解这一段。

先往返顾一下上文的一个概念:int 0x41

在PC机中BIOS设定的中断向量表中int 0x41的中断向量位置 (4*0x41 = 0x0000:0x0104​)寄存的并不是中断法式的地址,而是第一个硬盘的根基参数表。对于100%兼容的BIOS来说,==这里寄存着硬盘参数表阵列的首地址0xF000:0E401==,第二个硬盘的根基参数表进口地址存于int 0x46中断向量位置处.每个硬盘参数表有16个字节巨细.

这段话是重点,我之前误了解为磁盘参数就寄存在以0x0000:0x0104为首地址的单元中,总共占16个字节,但现实上,只存了4个字节,里面寄存的是磁盘参数表的偏移地址和段地址,也就是上文所说==这里寄存着硬盘参数表阵列的首地址0xF000:0E401==。

lds si,[4*0x41]

再看这行代码便可以了解了,这里是把0x0000:0x0104单元寄存的值(暗示硬盘参数表阵列的首地址的偏移地址)赋给si寄存器,把0x0000:0x0106单元寄存的值(暗示硬盘参数表阵列的首地址的段地址)赋给ds寄存器。

  1. 参数以十六进制方式显现

先说说浪费我很长时候的我的毛病:我想的是一个ASCII码8位,为什么答案里是4位4位输出,这里是搞清楚显现的目标。显现的是存在内存单元里的16进制数,例如某个字(2个字节)中的数值为 019A​,我所要显现的不是01和9A暗示的ASCII码,而是显现019A自己,所以要4位4位显现。

以十六进制方式显现比力简单。这是由于十六进制与二进制有很好的对应关系(每 4 位二进制数和 1 位十六进制数存在逐一对应关系),显现时只需将原二进制数每 4 位划成一组,按组求对应的 ASCII 码送显现器即可。ASCII 码与十六进制数字的对应关系为:0x30 ~ 0x39 对应数字 0 ~ 9,0x41 ~ 0x46 对应数字 a ~ f。从数字 9 到 a,其 ASCII 码间隔了 7h,这一点在转换时要出格留意。为使一个十六进制数能按高位到低位依次显现,现实编程中,需对 bx 中的数每次循环左移一组(4 位二进制),然后屏障掉当前高 12 位,对当前余下的 4 位(即 1 位十六进制数)求其 ASCII 码,要判定它是 0 ~ 9 还是 a ~ f,是前者则加 0x30 得对应的 ASCII 码,后者则要加 0x37 才行,最初送显现器输出。以上步调反复 4 次,便可以完成 bx 中数以 4 位十六进制的形式显现出来。

下面是供给的参考代码

INITSEG = 0x9000 ! 参数寄存位置的段地址
entry _start
_start:
! 打印 "NOW we are in SETUP"
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#25
mov bx,#0x0007
mov bp,#msg2
mov ax,cs
mov es,ax
mov ax,#0x1301
int 0x10

! 获得光标位置
mov ax,#INITSEG
mov ds,ax
mov ah,#0x03
xor bh,bh
int 0x10 ! 返回:dh = 行号;dl = 列号
mov [0],dx ! 存储到内存0x9000:0处

! 获得内存巨细
mov ah,#0x88
int 0x15 ! 返回:ax = 从0x100000(1M)处起头的扩大内存巨细(KB)
mov [2],ax ! 将扩大内存数值寄存在0x90002处(1个字)

! 读第一个磁盘参数表复制到0x90004处
mov ax,#0x0000
mov ds,ax
lds si,[4*0x41] ! 把低字(2B)置为偏移地址,高字(2B)置为段地址
mov ax,#INITSEG
mov es,ax
mov di,#0x0004
mov cx,#0x10 ! 反复16次,即传送16B
rep
movsb ! 按字节传送

! 打印前的预备
mov ax,cs
mov es,ax
mov ax,#INITSEG
mov ds,ax

! 打印"Cursor position:"
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#18
mov bx,#0x0007
mov bp,#msg_cursor
mov ax,#0x1301
int 0x10

! 打印光标位置
mov dx,[0]
call print_hex

! 打印"Memory Size:"
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#14
mov bx,#0x0007
mov bp,#msg_memory
mov ax,#0x1301
int 0x10

! 打印内存巨细
mov dx,[2]
call print_hex

! 打印"KB"
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#2
mov bx,#0x0007
mov bp,#msg_kb
mov ax,#0x1301
int 0x10

! 打印"Cyls:"
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#7
mov bx,#0x0007
mov bp,#msg_cyles
mov ax,#0x1301
int 0x10

! 打印柱面数
mov dx,[4]
call print_hex

! 打印"Heads:"
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#8
mov bx,#0x0007
mov bp,#msg_heads
mov ax,#0x1301
int 0x10

! 打印磁头数
mov dx,[6]
call print_hex

! 打印"Sectors:"
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#10
mov bx,#0x0007
mov bp,#msg_sectors
mov ax,#0x1301
int 0x10
mov dx,[18]
call print_hex

inf_loop:
jmp inf_loop

! 上面的call都转到这里
print_hex:
mov cx,#4 ! dx(16位)可以显现4个十六进制数字
print_digit:
rol dx,#4 ! 取 dx 的高4比特移到低4比特处
mov ax,#0xe0f ! ah = 请求的功用值(显现单个字符),al = 半字节(4个比特)掩码
and al,dl ! 前4位会被置为0
add al,#0x30 ! 给 al 数字加上十六进制 0x30
cmp al,#0x3a ! 比力看能否大于数字十
jl outp ! 是一个不大于十的数字则跳转
add al,#0x07 ! 否则就是a~f,要多加7
outp:
int 0x10 ! 显现单个字符
loop print_digit ! 反复4次
ret

! 打印换行回车
print_nl:
mov ax,#0xe0d ! CR
int 0x10
mov al,#0xa ! LF
int 0x10
ret

msg2:
.byte 13,10
.ascii "NOW we are in SETUP"
.byte 13,10,13,10
msg_cursor:
.byte 13,10
.ascii "Cursor position:"
msg_memory:
.byte 13,10
.ascii "Memory Size:"
msg_cyles:
.byte 13,10
.ascii "Cyls:"
msg_heads:
.byte 13,10
.ascii "Heads:"
msg_sectors:
.byte 13,10
.ascii "Sectors:"
msg_kb:
.ascii "KB"

.org 510
boot_flag:
.word 0xAA55

经过冗长的调试,获得以下成果




在这里插入图片描写

Memory Size 是 0x3C00KB,算一算恰好是 15MB(扩大内存),加上 1MB 恰好是 16MB,看看 Bochs 设置文件 bochs/bochsrc.bxrc:




image-20210814222912950

这些都和上面打出的参数符合,暗示此次尝试是成功的。

天道酬勤

尝试二总共花费25小时,由于没有汇编根本,花费了大量时候在了解代码上,希望下面的尝试可以越做越快吧。

尝试三 系统挪用

提醒

此次尝试触及的宏过于复杂,加上本人才能有限,我也没有花大量时候去研讨每一段代码,只是了解到每一段代码做了什么这一水平。

尝试目标

此次尝试的根基内容是:在 Linux 0.11 上增加两个系统挪用,并编写两个简单的利用法式测试它们。

  1. iam()
    第一个系统挪用是 iam(),其原型为:
    int iam(const char * name);

    完成的功用是将字符串参数 name 的内容拷贝到内核中保存下来。要求 name 的长度不能跨越 23 个字符。返回值是拷贝的字符数。假如 name 的字符个数跨越了 23,则返回 “-1”,并置 errno 为 EINVAL。
  2. whoami()
    第二个系统挪用是 whoami(),其原型为:
    int whoami(char* name, unsigned int size);

    它将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对 name 越界访存(name 的巨细由 size 说明)。返回值是拷贝的字符数。假如 size 小于需要的空间,则返回“-1”,并置 errno 为 EINVAL。

利用法式若何挪用系统挪用

在凡是情况下,挪用系统挪用和挪用一个普通的自界说函数在代码上并没有什么区分,但挪用后发生的工作有很大分歧。

挪用自界说函数是经过 call 指令间接跳转到该函数的地址,继续运转。

而挪用系统挪用,是挪用系统库中为该系统挪用编写的一个接口函数,叫 API(APPlication Programming Interface)。API 并不能完成系统挪用的真正功用,它要做的是去挪用实在的系统挪用,进程是:

  • 把系统挪用的编号存入 EAX;
  • 把函数参数存入别的通用寄存器;
  • 触发 0x80 号中断(int 0x80)。

linux-0.11 的 lib 目录下有一些已经实现的 API。Linus 编写它们的缘由是在内核加载终了后,会切换到用户形式下,做一些初始化工作,然后启动 shell。而用户形式下的很多工作需要依靠一些系统挪用才能完成,是以在内核中实现了这些系统挪用的 API。

我们无妨看看 lib/close.c,研讨一下 close() 的 API:

#define __LIBRARY__#include <unistd.h>_syscall1(int, close, int, fd)

其中 _syscall1 是一个宏,在 include/unistd.h 中界说。

#define _syscall1(type,name,atype,a) \type name(atype a) \{ \long __res; \__asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(a))); \if (__res >= 0) \ return (type) __res; \errno = -__res; \return -1; \}

_syscall1(int,close,int,fd) 停止宏展开,可以获得:

int close(int fd){ long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_close),"b" ((long)(fd))); if (__res >= 0) return (int) __res; errno = -__res; return -1;}

这就是 API 的界说。它先将宏 __NR_close 存入 EAX,将参数 fd 存入 EBX,然落后行 0x80 中断挪用。挪用返回后,从 EAX 取出返回值,存入 __res,再经过对 __res 的判定决议传给 API 的挪用者什么样的返回值。

其中 __NR_close 就是系统挪用的编号,在 include/unistd.h 中界说:

#define __NR_close 6/*所以增加系统挪用时需要点窜include/unistd.h文件,使其包括__NR_whoami和__NR_iam。*/

/*而在利用法式中,要有:*//* 有它,_syscall1 等才有用。详见unistd.h */#define __LIBRARY__/* 有它,编译器才能获知自界说的系统挪用的编号 */#include "unistd.h"/* iam()在用户空间的接口函数 */_syscall1(int, iam, const char*, name);/* whoami()在用户空间的接口函数 */_syscall2(int, whoami,char*,name,unsigned int,size);

在 0.11 情况下编译 C 法式,包括的头文件都在 /usr/include 目录下。

该目录下的 unistd.h 是标准头文件(它和 0.11 源码树中的 unistd.h 并不是同一个文件,虽然内容能够不异),没有 __NR_whoami__NR_iam 两个宏,需要手工加上它们,也可以间接从修悔改的 0.11 源码树中拷贝新的 unistd.h 过来。

从“int 0x80”进入内核函数

int 0x80 触发后,接下来就是内核的中断处置了。先领会一下 0.11 处置 0x80 号中断的进程。

在内核初始化时,主函数在 init/main.c 中,挪用了 sched_init() 初始化函数:

void main(void){// …… time_init(); sched_init(); buffer_init(buffer_memory_end);// ……}

sched_init()kernel/sched.c 中界说为:

void sched_init(void){// …… set_system_gate(0x80,&system_call);}

set_system_gate 是个宏,在 include/asm/system.h 中界说为:

#define set_system_gate(n,addr) \ _set_gate(&idt[n],15,3,addr)

_set_gate 的界说是:

#define _set_gate(gate_addr,type,dpl,addr) \__asm__ ("movw %%dx,%%ax\n\t" \ "movw %0,%%dx\n\t" \ "movl %%eax,%1\n\t" \ "movl %%edx,%2" \ : \ : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ "o" (*((char *) (gate_addr))), \ "o" (*(4+(char *) (gate_addr))), \ "d" ((char *) (addr)),"a" (0x00080000))

虽然看起来挺麻烦,但现实上很简单,就是填写 IDT(中断描写符表),将 system_call 函数地址写到 0x80 对应的中断描写符中,也就是在中断 0x80 发生后,自动挪用函数 system_call

接下来看 system_call。该函数纯汇编打造,界说在 kernel/system_call.s 中:

!……! # 这是系统挪用总数。假如增删了系统挪用,必须做响应点窜nr_system_calls = 72!…….globl system_call.align 2system_call:! # 检查系统挪用编号能否在正当范围内 cmpl \$nr_system_calls-1,%eax ja bad_sys_call push %ds push %es push %fs pushl %edx pushl %ecx! # push %ebx,%ecx,%edx,是传递给系统挪用的参数 pushl %ebx! # 让ds, es指向GDT,内核地址空间 movl $0x10,%edx mov %dx,%ds mov %dx,%es movl $0x17,%edx! # 让fs指向LDT,用户地址空间 mov %dx,%fs call sys_call_table(,%eax,4) pushl %eax movl current,%eax cmpl $0,state(%eax) jne reschedule cmpl $0,counter(%eax) je reschedule

system_call.globl 修饰为其他函数可见。

call sys_call_table(,%eax,4) 之前是一些压栈庇护,点窜段挑选子为内核段,call sys_call_table(,%eax,4) 以后是看看能否需要重新调剂,这些都与本尝试没有间接关系,此处只关心 call sys_call_table(,%eax,4) 这一句。

按照汇编寻址方式它现实上是:call sys_call_table + 4 * %eax,其中 eax 中放的是系统挪用号,即 __NR_xxxxxx

明显,sys_call_table 一定是一个函数指针数组的肇端地址,它界说在 include/linux/sys.h 中:

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,...

增加尝试要求的系统挪用,需要在这个函数表中增加两个函数援用 ——sys_iamsys_whoami。固然该函数在 sys_call_table 数组中的位置必须和 __NR_xxxxxx 的值对应上。

同时还要模仿此文件中前面各个系统挪用的写法,加上:

extern int sys_whoami();extern int sys_iam();

否则,编译会出错的。

实现 sys_iam() 和 sys_whoami()

增加系统挪用的最初一步,是在内核中实现函数 sys_iam()sys_whoami()

每个系统挪用都有一个 sys_xxxxxx() 与之对应,它们都是我们进修和模仿的好工具。

比如在 fs/open.c 中的 sys_close(int fd)

int sys_close(unsigned int fd){// …… return (0);}

它没有什么特此外,都是实实在在地做 close() 该做的工作。

所以只要自己建立一个文件:kernel/who.c,然后实现两个函数就高枕无忧了。

依照上述逻辑点窜响应文件

经过上文描写,我们已司理清楚了要点窜的地方在那里

  1. 增加iam和whoami系统挪用编号的宏界说(_NR_xxxxxx),文件:include/unistd.h



image-20210829143749907

  1. 点窜系统挪用总数, 文件:kernel/system_call.s



image-20210829144114522

  1. 为新增的系统挪用增加系统挪用名并保护系统挪用表,文件:include/linux/sys.h



image-20210829144844784

  1. 为新增的系统挪用编写代码实现,在linux-0.11/kernel目录下,建立一个文件 who.c
    #include <asm/segment.h>#include <errno.h>#include <string.h>char _myname[24];int sys_iam(const char *name){ char str[25]; int i = 0; do { // get char from user input str[i] = get_fs_byte(name + i); } while (i <= 25 && str[i++] != '\0'); if (i > 24) { errno = EINVAL; i = -1; } else { // copy from user mode to kernel mode strcpy(_myname, str); } return i;}int sys_whoami(char *name, unsigned int size){ int length = strlen(_myname); printk("%s\n", _myname); if (size < length) { errno = EINVAL; length = -1; } else { int i = 0; for (i = 0; i < length; i++) { // copy from kernel mode to user mode put_fs_byte(_myname[i], name + i); } } return length;}

点窜 Makefile

要想让我们增加的 kernel/who.c 可以和别的 Linux 代码编译链接到一路,必必要点窜 Makefile 文件。

Makefile 里记录的是一切源法式文件的编译、链接法则,《正文》3.6 节有简单先容。我们之所以简单地运转 make 便可以编译全部代码树,是由于 make 完全依照 Makefile 里的指示工作。

Makefile 在代码树中有很多,别离负责分歧模块的编译工作。我们要点窜的是 kernel/Makefile。需要点窜两处。

(1)第一处

OBJS = sched.o system_call.o traps.o asm.o fork.o \ panic.o printk.o vsprintf.o sys.o exit.o \ signal.o mktime.o

改成:

OBJS = sched.o system_call.o traps.o asm.o fork.o \ panic.o printk.o vsprintf.o sys.o exit.o \ signal.o mktime.o who.o

增加了 who.o




image-20210829161702940

(2)第二处

### Dependencies:exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \ ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \ ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \ ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \ ../include/asm/segment.h

改成:

### Dependencies:who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.hexit.s exit.o: exit.c ../include/errno.h ../include/signal.h \ ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \ ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \ ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \ ../include/asm/segment.h

增加了 who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h




image-20210829162022371

Makefile 点窜后,战争常一样 make all 就能自动把 who.c 加入到内核中了。

编写测试法式

到此为止,内核中需要点窜的部分已经完成,接下来需要编写测试法式来考证新增的系统挪用能否已经被编译到linux-0.11内核可供挪用。首先在oslab目录下编写iam.c,whoami.c

/* iam.c */
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <asm/segment.h>
#include <linux/kernel.h>
_syscall1(int, iam, const char*, name);

int main(int argc, char *argv[])
{
/*挪用系统挪用iam()*/
iam(argv[1]);
return 0;
}

/* whoami.c */
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <asm/segment.h>
#include <linux/kernel.h>
#include <stdio.h>

_syscall2(int, whoami,char *,name,unsigned int,size);

int main(int argc, char *argv[])
{
char username[64] = {0};
/*挪用系统挪用whoami()*/
whoami(username, 24);
printf("%s\n", username);
return 0;
}

以上两个文件需要放到启动后的linux-0.11操纵系统上运转,考证新增的系统挪用能否有用,那若何才能将这两个文件从宿主机转到稍后虚拟机中启动的linux-0.11操纵系统上呢?这里我们采用挂载方式实现宿主机与虚拟机操纵系统的文件同享,在 oslab 目录下履行以下号令挂载hdc目录到虚拟机操纵系统上。

sudo ./mount-hdc

再经过以下号令将上述两个文件拷贝到虚拟机linux-0.11操纵系统/usr/root/目录下,号令在oslab/目录下履行:

cp iam.c whoami.c hdc/usr/root

假如方针目录下存在对应的两个文件则可启动虚拟机停止测试了。

  • 编译
    [/usr/root]# gcc -o iam iam.c
    [/usr/root]# gcc -o whoami whoami.c
  • 运转测试
    [/usr/root]# ./iam wcf
    [/usr/root]# ./whoami

号令履行后,极能够会报以下毛病:




image-20210829172115712

这代表==虚拟机==操纵系统中/usr/include/unistd.h文件中没有新增的系统挪用挪用号

为新增系统挪用设备挪用号

#define __NR_whoami 72#define __NR_iam 73

再次履行:




image-20210829215630491

尝试成功



  • 为什么这里会打印2次?
  • 由于在系统内核中履行了 printk() 函数,在用户形式下又履行了一次 printf() 函数。

要晓得到,printf() 是一个只能在用户形式下履行的函数,而系统挪用是在内核形式中运转,所以 printf() 不成用,要用 printk()。

printk()printf() 的接口和功用基底细同,只是代码上有一点点分歧。printk() 需要出格处置一下 fs 寄存器,它是公用于用户形式的段寄存器。




image-20210829220203825



image-20210829220217721

天道酬勤

尝试三总共花费7小时,看的不是出格仔细,没有出格深入的进修宏展开和内联汇编。但根基了解了系统挪用的目标和方式,Linus永久的神!

尝试四 进程运转轨迹的跟踪与统计

尝试目标

  • 把握 Linux 下的多进程编程技术;
  • 经过对进程运转轨迹的跟踪来形象化进程的概念;
  • 在进程运转轨迹跟踪的根本上停止响应的数据统计,从而能对进程调剂算法停止现实的量化评价,更进一步加深对换剂和调剂算法的了解,获得能在现实操纵系统上对换剂算法停止尝试数据对照的间接经历。

尝试内容

进程从建立(Linux 下挪用 fork())到竣事的全部进程就是进程的生命期,进程在其生命期中的运转轨迹现实上就表示为进程状态的屡次切换,如进程建立今后会成为停当态;当该进程被调剂今后会切换到运转态;在运转的进程中假如启动了一个文件读写操纵,操纵系统会将该进程切换到阻塞态(期待态)从而让出 CPU;当文件读写终了今后,操纵系统会在将其切换成停当态,期待进程调剂算法来调剂该进程履行……

本次尝试包括以下内容:

  • 基于模板 process.c 编写多进程的样本法式,实现以下功用: + 一切子进程都并交运转,每个子进程的现实运转时候一般不跨越 30 秒; + 父进程向标准输出打印一切子进程的 id,并在一切子进程都退出后才退出;
  • Linux0.11 上实现进程运转轨迹的跟踪。 + 根基使命是在内核中保护一个日志文件 /var/process.log,把从操纵系统启动到系统关机进程中一切进程的运转轨迹都记录在这一 log 文件中。
  • 在修悔改的 0.11 上运转样本法式,经过度析 log 文件,统计该法式建立的一切进程的期待时候、完成时候(周转时候)和运转时候,然后计较均匀期待时候,均匀完成时候和吞吐量。可以自己编写统计法式停止统计。
  • 点窜 0.11 进程调剂的时候片,然后再运转一样的样本法式,统计一样的时候数据,和原本的情况对照,体味分歧时候片带来的差别。

/var/process.log 文件的格式必须为:

pid X time

其中:

  • pid 是进程的 ID;
  • X 可所以 N、J、R、W 和 E 中的肆意一个,别离暗示进程新建(N)、进入停当态(J)、进入运转态(R)、进入阻塞态(W) 和退出(E);
  • time 暗示 X 发生的时候。这个时候不是物理时候,而是系统的滴答时候(tick);

三个字段之间用制表符分隔。例如:

12 N 105612 J 10574 W 105712 R 105713 N 105813 J 105914 N 105914 J 106015 N 106015 J 106112 W 106115 R 106115 J 107614 R 107614 E 1076......

编写process.c文件

提醒

在 Ubuntu 下,top 号令可以监视立即的进程状态。在 top 中,按 u,再输入你的用户名,可以限制只显现以你的身份运转的进程,更方便观察。按 h 可获得帮助。




image-20210903111253440

在 Ubuntu 下,ps 号令可以显现那时各个进程的状态。ps aux 会显现一切进程;ps aux | grep xxxx 将只显现名为 xxxx 的进程。更具体的用法叨教 man。




image-20210903111426774

在 Linux 0.11 下,按 F1 可以立即显现当前一切进程的状态。




image-20210903111506016

文件首要感化

process.c文件首要实现了一个函数:

/*
* 此函数依照参数占用CPU和I/O时候
* last: 函数现实占用CPU和I/O的总时候,不含在停当行列中的时候,>=0是必须的
* cpu_time: 一次持续占用CPU的时候,>=0是必须的
* io_time: 一次I/O消耗的时候,>=0是必须的
* 假如last > cpu_time + io_time,则来去屡次占用CPU和I/O,直到总运转时候跨越last为止
* 一切时候的单元为秒
*/
cpuio_bound(int last, int cpu_time, int io_time);

下面是 4 个利用的例子:

// 比如一个进程假如要占用10秒的CPU时候,它可以挪用:
cpuio_bound(10, 1, 0);
// 只要cpu_time>0,io_time=0,结果不异
// 以I/O为首要使命:
cpuio_bound(10, 0, 1);
// 只要cpu_time=0,io_time>0,结果不异
// CPU和I/O各1秒钟循环:
cpuio_bound(10, 1, 1);
// 较多的I/O,较少的CPU:
// I/O时候是CPU时候的9倍
cpuio_bound(10, 1, 9);

点窜此模板,用 fork() 建立多少个同时运转的子进程,父进程期待一切子进程退出后才退出,每个子进程依照你的志愿做分歧或不异的 cpuio_bound(),从而完成一个本性化的样本法式。

它可以用来检验有关 log 文件的点窜能否正确,同时还是数据统计工作的根本。

wait() 系统挪用可以让父进程期待子进程的退出。

关键函数诠释

fork()

这里摘录某位博主的详解操纵系统之 fork() 函数详解 - 简书 (jianshu.com)

fork()函数经过系统挪用建立一个与本来进程几近完全不异的进程,也就是两个进程可以做完全不异的事,但假如初始参数大概传入的变量分歧,两个进程也可以做分歧的事。

一个进程挪用fork()函数后,系统先给新的进程分派资本,例如存储数据和代码的空间。然后把本来的进程的一切值都复制到新的新进程中,只要少数值与本来的进程的值分歧。相当于克隆了一个自己。

#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t fpid; //fpid暗示fork函数返回的值
int count=0;
fpid=fork();
if (fpid < 0)
printf("error in fork!");
else if (fpid == 0) {
printf("i am the child process, my process id is %d/n",getpid());
printf("我是爹的儿子/n");//对某些人来说中文看着更直白。
count++;
}
else {
printf("i am the parent process, my process id is %d/n",getpid());
printf("我是孩子他爹/n");
count++;
}
printf("统计成果是: %d/n",count);
return 0;
}

运转成果:

i am the child process, my process id is 5574
我是爹的儿子
统计成果是: 1
i am the parent process, my process id is 5573
我是孩子他爹
统计成果是: 1

在语句fpid=fork()之前,只要==一个==进程在履行这段代码,但在这条语句以后,就酿成==两个==进程在履行了,这两个进程的几近完全不异,将要履行的下一条语句都是==if(fpid<0)==

为什么两个进程的fpid分歧呢,这与fork函数的特征有关。fork挪用的一个奇妙之处就是它仅仅被挪用一次,却可以返回两次,它能够有三种分歧的返回值:
1)在父进程中,fork返回新建立子进程的进程ID;
2)在子进程中,fork返回0;
3)假如出现毛病,fork返回一个负值;

在fork函数履行终了后,假如建立新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新建立子进程的进程ID。我们可以经过fork返回的值来判定当进步程是子进程还是父进程。

援用一位网友的话来诠释fpid的值为什么在父子进程平分歧。“实在就相当于链表,进程构成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 由于子进程没有子进程,所以其fpid为0.
  fork出错能够有两种缘由:
 1)当前的进程数已经到达了系统规定的上限,这时errno的值被设备为EAGAIN。
 2)系统内存不敷,这时errno的值被设备为ENOMEM。
 建立新进程成功后,系统中出现两个根基完全不异的进程,这两个进程履行没有牢固的前后顺序,哪个进程先履行要看系统的进程调剂战略。
 每个进程都有一个怪异(互不不异)的进程标识符(process ID),可以经过getpid()函数获得,还有一个记录父进程pid的变量,可以经过getppid()函数获得变量的值。
  fork履行终了后,出现两个进程




img

有人说两个进程的内容完全一样啊,怎样打印的成果纷歧样啊,那是由于判定条件的缘由,上面罗列的只是进程的代码和指令,还有变量啊。
 履行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是自力的,存在分歧的地址中,不是共用的,这点要留意。可以说,我们就是经过fpid来识别和操纵父子进程的。
 还有人能够迷惑为什么不是从#include处起头复制代码的,这是由于fork是把进程当前的情况拷贝一份,履行fork时,进程已经履行完了int count=0;fork只拷贝下一个要履行的代码到新的进程。

struct tms 结构体

struct tms 结构体界说在 <sys/times.h> 头文件里,具体界说以下:

援用
/* Structure describing CPU time used by a process and its children. */
struct tms
{
clock_t tms_utime ; /* User CPU time. 用户法式 CPU 时候*/
clock_t tms_stime ; /* System CPU time. 系统挪用所花费的 CPU 时候 */

clock_t tms_cutime ; /* User CPU time of dead children. 已死掉子进程的 CPU 时候*/
clock_t tms_cstime ; /* System CPU time of dead children. 已死掉子进程所花费的系统挪用 CPU 时候*/
};

用户CPU时候和系统CPU时候之和为CPU时候,即号令占用CPU履行的时候总和。现实时候要大于CPU时候,由于Linux是多使命操纵系统,常常在履行一条号令时,系统还要处置其他使命。另一个需要留意的题目是即使每次履行不异的号令,所花费的时候也纷歧定不异,由于其花费的时候与系统运转相关。

数据范例 clock_t

关于该数据范例的界说以下:

#ifndef _CLOCK_T_DEFINEDtypedef long clock_t;#define _CLOCK_T_DEFINED#endif

clock_t 是一个长整型数。

在 time.h 文件中,还界说了一个常量 CLOCKS_PER_SEC ,它用来暗示一秒钟会有几多个时钟计时单元,其界说以下:

#define CLOCKS_PER_SEC ((clock_t)1000)

下文就模拟cpu操纵,界说 HZ=100​​,内核的标定时候是jiffy,一个jiffy就是一个内部时钟周期,而内部时钟周期是由100HZ的频次所发生中的,也就是一个时钟滴答,间隔时候是10毫秒(ms).计较出来的时候也并非实在时候,而是时钟滴答次数,乘以10ms可以获得实在的时候。

代码实现

下面给出代码:

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/times.h>

#define HZ 100

void cpuio_bound(int last, int cpu_time, int io_time);

int main(int argc, char * argv[])
{
pid_t n_proc[10]; /*10个子进程 PID*/
int i;
for(i=0;i<10;i++)
{
n_proc[i] = fork();
/*子进程*/
if(n_proc[i] == 0)
{
cpuio_bound(20,2*i,20-2*i); /*每个子进程都占用20s*/
return 0; /*履行完cpuio_bound 今后,竣事该子进程*/
}
/*fork 失利*/
else if(n_proc[i] < 0 )
{
printf("Failed to fork child process %d!\n",i+1);
return -1;
}
/*父进程继续fork*/
}
/*打印一切子进程PID*/
for(i=0;i<10;i++)
printf("Child PID: %d\n",n_proc[i]);
/*期待一切子进程完成*/
wait(&i); /*Linux 0.11 上 gcc要求必须有一个参数, gcc3.4+则不需要*/
return 0;
}

/*
* 此函数依照参数占用CPU和I/O时候
* last: 函数现实占用CPU和I/O的总时候,不含在停当行列中的时候,>=0是必须的
* cpu_time: 一次持续占用CPU的时候,>=0是必须的
* io_time: 一次I/O消耗的时候,>=0是必须的
* 假如last > cpu_time + io_time,则来去屡次占用CPU和I/O
* 一切时候的单元为秒
*/
void cpuio_bound(int last, int cpu_time, int io_time)
{
struct tms start_time, current_time;
clock_t utime, stime;
int sleep_time;

while (last > 0)
{
/* CPU Burst */
times(&start_time);
/* 实在只要t.tms_utime才是实在的CPU时候。但我们是在模拟一个
* 只在用户状态运转的CPU大户,就像“for(;;);”。所以把t.tms_stime
* 加上很公道。*/
do
{
times(&current_time);
utime = current_time.tms_utime - start_time.tms_utime;
stime = current_time.tms_stime - start_time.tms_stime;
} while ( ( (utime + stime) / HZ ) < cpu_time );
last -= cpu_time;

if (last <= 0 )
break;

/* IO Burst */
/* 用sleep(1)模拟1秒钟的I/O操纵 */
sleep_time=0;
while (sleep_time < io_time)
{
sleep(1);
sleep_time++;
}
last -= sleep_time;
}
}




image-20210904084620417

答疑

:question: 为什么说它可以用来检验有关 log 文件的点窜能否正确,同时还是数据统计工作的根本?

  • 每个子进程都经过 cpuio_bound 函数实现了占用CPU和I/O时候的操纵,而且可以切确的晓得每个操纵的时候。所以下面的 log 文件(日志文件)正确与否可以借此推算。

尽早翻开log文件

操纵系统启动后先要翻开 /var/process.log,然后在每个进程发生状态切换的时辰向 log 文件内写入一笔记录,其进程和用户态的利用法式没什么两样。但是,由于内核状态的存在,使进程中的很多细节变得完全纷歧样。

为了能尽夙起头记录,该当在内核启动时就翻开 log 文件。内核的进口是 init/main.c 中的 main(),其中一段代码是:

//……
move_to_user_mode();
if (!fork()) { /* we count on this going ok */
init();
}
//……

这段代码在进程 0 中运转,先切换到用户形式,然后全系统第一次挪用 fork() 建立进程 1。进程 1 挪用 init()

在 init()中:

// ……//加载文件系统setup((void *) &drive_info);// 翻开/dev/tty0,建立文件描写符0和/dev/tty0的关联(void) open("/dev/tty0",O_RDWR,0);// 让文件描写符1也和/dev/tty0关联(void) dup(0);// 让文件描写符2也和/dev/tty0关联(void) dup(0);// ……

这段代码建立了文件描写符 0、1 和 2,它们别离就是 stdin、stdout 和 stderr。这三者的值是系统标准(Windows 也是如此),不成改变。

可以把 log 文件的描写符关联到 3。文件系统初始化,描写符 0、1 和 2 关联以后,才能翻开 log 文件,起头记录进程的运转轨迹。

为了能尽早拜候 log 文件,我们要让上述工作在进程 0 中就完成。所以把这一段代码从 init() 移动到 main() 中,放在 move_to_user_mode() 以后(不能再靠前了),同时加上翻开 log 文件的代码。

点窜后的 main() 以下:

//……
move_to_user_mode();

/***************增加起头***************/
setup((void *) &drive_info);

// 建立文件描写符0和/dev/tty0的关联
(void) open("/dev/tty0",O_RDWR,0);

//文件描写符1也和/dev/tty0关联
(void) dup(0);

// 文件描写符2也和/dev/tty0关联
(void) dup(0);

(void) open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY,0666);

/***************增加竣事***************/

if (!fork()) { /* we count on this going ok */
init();
}
//……

翻开 log 文件的参数的寄义是建立只写文件,假如文件已存在则清空已有内容。文件的权限是一切人可读可写。

这样,文件描写符 0、1、2 和 3 就在进程 0 中建立了。按照 fork() 的道理,进程 1 会继续这些文件描写符,所以 init() 中就不必再 open() 它们。尔后一切新建的进程都是进程 1 的子孙,也会继续它们。但现实上,init() 的后续代码和 /bin/sh

高端人脉微信群

高端人脉微信群

人脉=钱脉,我们相信天下没有聚不拢的人脉,扫码进群找到你所需的人脉,对接你所需的资源。

商业合作微信

商业合作微信

本站创始人微信,13年互联网营销经验,擅长引流裂变、商业模式、私域流量,高端人脉资源丰富。

我有话说......
  • sy-yun 2022-11-24 21:31
    为什么实验2 中

    $ as86 -0 -a -o bootsect.o bootsect.s
    $ ld86 -0 -s -o bootsect bootsect.o
    编译完,bootsect变成548个字节,然后去除36字节之后运行run说no bootable device
  • juvefy 2022-11-24 21:30
    大佬有点强啊
  • 天一教育培训寐 2022-11-24 21:30
    实验六的sem.c中sys_sem_open,求kernelname的总长度之前在数组末尾加个\0,不然总长度求得可能有误,因为这个我在实验七栽了个大坑[飙泪笑]
  • Liusijia0813 2022-11-24 21:29
    我做完《实验五 基于内核栈切换的进程切换》,一运行就一直在loading system那里一直刷新,okok的,博主有遇到过这种情况吗?
  • limingnan18 2022-11-24 21:29
    那就去学啊,看王爽老师的书
  • 伊索谗言 2022-11-24 21:29
    哈工大这个操作系统课我不会汇编前期听不懂怎么办啊[捂脸]

查看全部评论>>

相关推荐

国产操作系统发布:手机、电脑应用都能兼容

国产操作系统发布:手机、电脑应用都能兼容

近日深度操作系统官方宣布,国产操作系统deepin 20.6版本正式上线,新版本升级了Stabl

键盘操作方法大全

键盘操作方法大全

【键盘操作方法大全】键盘可不仅仅能帮我们打字哦,还有很多快捷的操作你都知道吗?除

电脑的一些基本常识和操作

电脑的一些基本常识和操作

关于电脑的一些基本常识和操作(电脑初学者必备)  众所周知,在21世纪的今天,电脑

操作系统实验一到实验九合集(哈工大李治军)

操作系统实验一到实验九合集(哈工大李治军)

知乎Markdown适配不行,希望在我的博客中查看文章作者寄语操作系统实验的学习是一个循

看完这篇Linux基本的操作就会了

看完这篇Linux基本的操作就会了

前言只有光头才能变强这个学期开了Linux的课程了,授课的老师也是比较负责任的一位。

搞懂软考,看这一篇就够了

搞懂软考,看这一篇就够了

大家好,我是你们的新朋友叨叨张,很高兴能够在这里和大家相遇,今天我要分享的主题是

0基础入门Photoshop基础操作(一)

0基础入门Photoshop基础操作(一)

大家好我是正经人你以为上来就要教封面上那个效果吗?当然不是,那个是我好几年前做的

从操作系统的进化中,读懂MagicOS

从操作系统的进化中,读懂MagicOS

操作系统的数十年沉浮1946年诞生第一台计算机时,还没有操作系统。程序员靠着「打孔」

还不会使用 GitHub ? GitHub 教程来了!万字图文详解

还不会使用 GitHub ? GitHub 教程来了!万字图文详解

在编程届有个共识,想要成为一个合格的程序员必须要掌握 GitHub 的用法!接下来,我们

Git使用教程,最详细,最傻瓜,最浅显,真正手把手教

Git使用教程,最详细,最傻瓜,最浅显,真正手把手教

(预警:因为详细,所以行文有些长,新手边看边操作效果出乎你的预料)一:Git是什么

Win10系统常用的快捷键(绝对很详细)

Win10系统常用的快捷键(绝对很详细)

前言介绍快捷键,也就是刷刷按几下键盘上的组合键就可以达到鼠标点很多下才能实现的效

上海医保转入杭州- “浙里办”简易操作

上海医保转入杭州- “浙里办”简易操作

我在上篇文章说过,上海医保需要社保(即养老保险)成功转入杭州后才能进行转移,申请

史上最全高中化学实验总结(操作+方法)

史上最全高中化学实验总结(操作+方法)

高中化学实验真复杂,包学习APP为你整理最全总结,不怕记不住!一、中学化学实验操作

大学四年自学走来,关于怎么学「操作系统」和「计算机网络 ... ...

大学四年自学走来,关于怎么学「操作系统」和「计算机网络 ... .

最近收到不少读者留言,关于怎么学「操作系统」和「计算机网络」的留言,小林写这一块

用这6款软件记笔记,不要太爽!丨上进青年研习社

用这6款软件记笔记,不要太爽!丨上进青年研习社

文/小渔俗话说:“好记性不如烂笔头。”在无纸时代,记笔记当然也不一定要用烂笔头了

8个流氓软件,这辈子是不可能安装的。

8个流氓软件,这辈子是不可能安装的。

之前安利过不少值得安装或使用的软件,但这一次我想换个角度,写一些强烈不建议安装的

KMS服务,一句命令激活windows/office!

KMS服务,一句命令激活windows/office!

服务器地址:http://kms.03k.org(点击检查是否可用);服务作用:在线激活windows和off

Windows10 快速启动

Windows10 快速启动

从Windows8开始,Windows的开机速度有了极大的提高,这得益于一项新的功能:快速启动

最后教一次:完美解决电脑上的流氓软件

最后教一次:完美解决电脑上的流氓软件

国产流氓软件之所以流氓就流氓在 “ 买一赠N ”装一个软件,就会给你附赠N个流氓软件

推荐10个超好玩的网站,一打开就停不下来!

推荐10个超好玩的网站,一打开就停不下来!

推荐10个超好玩的网站,窥探别人的记忆,敲键盘听歌,办公偷懒神器,看中国古今妖怪…

TA还没有介绍自己。

电话咨询: 15924191378
添加微信