知乎Markdown适配不可,希望在我的博客中检察文章作者寄语操纵系统尝试的进修是一个循序渐进的进程,初度看linux-0.11中的代码,看着满屏的汇编说话,确切头疼。但经过进修赵炯博士的Linux内核0.11完全正文,连系着王爽教员的汇编说话一书,我逐步了解每段汇编说话的寄义和感化。本文主如果经过对哈工大李治军配套尝试的实现,侧重诠释每一段的汇编代码,使读者对尝试的整体头绪有一个初步的熟悉,不再由于害怕汇编而不放弃尝试。本文只是举一反三,希望读者可以深入研讨我下文供给的参考材料,做到理论与理论兼具。 参考材料
目录[TOC] 尝试一 熟悉尝试情况只是熟悉尝试情况,我没有益用蓝桥云课的尝试情况,而是经过阿里云办事器搭建了linux情况,有需要的可以看以下2篇文章
尝试二 操纵系统的指导Linux 0.11 文件夹中的 汇编常识简要整理了一下此次尝试所需的根本汇编常识,可以鄙人文阅读代码是碰到再回过甚来看! int 0x10image-20210808151038949 留意,这里ah要先有值,代表内部子法式的编号 功用号 ah=0x03,感化是读取光标的位置
功用号 ah=0x13,感化是显现字符串
功用号 ah=0x0e,感化是显现字符
int 0x13在DOS等实形式操纵系统下,挪用INT 13h会跳转到计较机的ROM-BIOS代码中停止低级磁盘办事,对法式停止基于物理扇区的磁盘读写操纵。 功用号 ah=0x02,感化是读磁盘扇区到内存
功用号 ah=0x00,感化是磁盘系统复位
这里我只挑了下文需要的先容,更多内容可以参考这篇博客BIOS系统办事 —— 间接磁盘办事(int 0x13) int 0x15功用号 ah=0x88,感化是获得系统所含扩大内存巨细
int 0x41在PC机中BIOS设定的中断向量表中
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位通用寄存器同时赋值 举例:
可以把上述代码了解为这样一个进程,但现实上不能这么写 mov AX,[100H]mov DS,[100H+2]即把低字(2B)置为偏移地址,高字(2B)置为段地址 DF标志和串传送指令flag的第10位是DF,偏向标志位。在串处置指令中,控制每次操纵后si、di的增减。
来看一个串传送指令
可以看出,movsb的功用是将 ds:si 指向的内存单元中的字节送入 es:di中,然后按照标志寄存器df位的值,将si和di递增或递加。 也可以传送一个字
可以看出,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 IPjmp near ptr 标号 retret指令用栈中的数据,点窜IP的内容,从而实现近转移 (1) (IP)=((ss)*16+(sp)) (2) (sp)=(sp)+2 CPU履行ret指令时,相当于停止: pop IP改写bootsect.s翻开 image-20210711135216130 image-20210814222810462
将 完整的代码以下: 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/目录 履行下面两个号令编译和链接 $ ld86 -0 -s -o bootsect bootsect.o image-20210711112503976 其中 需要留意的文件是 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),所以 image-20210814222824190 去掉这 32 个字节的文件头部 $ dd bs=1 if=bootsect of=Image skip=32天生的 Image 就是去掉文件头的 去掉这 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 接下来需要编写 一切需要的功用在原版 除了新增代码,我们还需要去掉在 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 的指引履行了
点窜工作首要集合在 image-20210814222844903 重新编译 $ cd ~/oslab/linux-0.11$ make BootImage $ ../run image-20210814222851074 setup.s获得根基硬件参数这里把一些难以了解的代码零丁列出来
这里花了我很长时候,缘由是概念没有搞清楚,我感觉教员在尝试指导书上写的也不是很清楚,CSDN上都只是草草复制的代码,感受他们可以压根没有了解这一段。 先往返顾一下上文的一个概念: 在PC机中BIOS设定的中断向量表中 这段话是重点,我之前误了解为磁盘参数就寄存在以0x0000:0x0104为首地址的单元中,总共占16个字节,但现实上,只存了4个字节,里面寄存的是磁盘参数表的偏移地址和段地址,也就是上文所说==这里寄存着硬盘参数表阵列的首地址0xF000:0E401==。 lds si,[4*0x41]再看这行代码便可以了解了,这里是把0x0000:0x0104单元寄存的值(暗示硬盘参数表阵列的首地址的偏移地址)赋给si寄存器,把0x0000:0x0106单元寄存的值(暗示硬盘参数表阵列的首地址的段地址)赋给ds寄存器。
先说说浪费我很长时候的我的毛病:我想的是一个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 上增加两个系统挪用,并编写两个简单的利用法式测试它们。
利用法式若何挪用系统挪用在凡是情况下,挪用系统挪用和挪用一个普通的自界说函数在代码上并没有什么区分,但挪用后发生的工作有很大分歧。 挪用自界说函数是经过 call 指令间接跳转到该函数的地址,继续运转。 而挪用系统挪用,是挪用系统库中为该系统挪用编写的一个接口函数,叫 API(APPlication Programming Interface)。API 并不能完成系统挪用的真正功用,它要做的是去挪用实在的系统挪用,进程是:
linux-0.11 的 lib 目录下有一些已经实现的 API。Linus 编写它们的缘由是在内核加载终了后,会切换到用户形式下,做一些初始化工作,然后启动 shell。而用户形式下的很多工作需要依靠一些系统挪用才能完成,是以在内核中实现了这些系统挪用的 API。 我们无妨看看 lib/close.c,研讨一下 其中 将 这就是 API 的界说。它先将宏 其中 /*而在利用法式中,要有:*//* 有它,_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 法式,包括的头文件都在 该目录下的 从“int 0x80”进入内核函数
在内核初始化时,主函数在
虽然看起来挺麻烦,但现实上很简单,就是填写 IDT(中断描写符表),将 接下来看
按照汇编寻址方式它现实上是: 明显, 增加尝试要求的系统挪用,需要在这个函数表中增加两个函数援用 —— 同时还要模仿此文件中前面各个系统挪用的写法,加上: extern int sys_whoami();extern int sys_iam();否则,编译会出错的。 实现 sys_iam() 和 sys_whoami()增加系统挪用的最初一步,是在内核中实现函数 每个系统挪用都有一个 比如在 它没有什么特此外,都是实实在在地做 所以只要自己建立一个文件: 依照上述逻辑点窜响应文件经过上文描写,我们已司理清楚了要点窜的地方在那里
image-20210829143749907
image-20210829144114522
image-20210829144844784
点窜 Makefile要想让我们增加的 Makefile 里记录的是一切源法式文件的编译、链接法则,《正文》3.6 节有简单先容。我们之所以简单地运转 make 便可以编译全部代码树,是由于 make 完全依照 Makefile 里的指示工作。 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增加了 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增加了 image-20210829162022371 Makefile 点窜后,战争常一样 编写测试法式到此为止,内核中需要点窜的部分已经完成,接下来需要编写测试法式来考证新增的系统挪用能否已经被编译到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操纵系统上呢?这里我们采用挂载方式实现宿主机与虚拟机操纵系统的文件同享,在 再经过以下号令将上述两个文件拷贝到虚拟机linux-0.11操纵系统/usr/root/目录下,号令在oslab/目录下履行: cp iam.c whoami.c hdc/usr/root假如方针目录下存在对应的两个文件则可启动虚拟机停止测试了。
号令履行后,极能够会报以下毛病: image-20210829172115712 这代表==虚拟机==操纵系统中/usr/include/unistd.h文件中没有新增的系统挪用挪用号 为新增系统挪用设备挪用号 #define __NR_whoami 72#define __NR_iam 73再次履行: image-20210829215630491 尝试成功
要晓得到,printf() 是一个只能在用户形式下履行的函数,而系统挪用是在内核形式中运转,所以 printf() 不成用,要用 printk()。
image-20210829220203825 image-20210829220217721 天道酬勤尝试三总共花费7小时,看的不是出格仔细,没有出格深入的进修宏展开和内联汇编。但根基了解了系统挪用的目标和方式,Linus永久的神! 尝试四 进程运转轨迹的跟踪与统计尝试目标
尝试内容进程从建立(Linux 下挪用 fork())到竣事的全部进程就是进程的生命期,进程在其生命期中的运转轨迹现实上就表示为进程状态的屡次切换,如进程建立今后会成为停当态;当该进程被调剂今后会切换到运转态;在运转的进程中假如启动了一个文件读写操纵,操纵系统会将该进程切换到阻塞态(期待态)从而让出 CPU;当文件读写终了今后,操纵系统会在将其切换成停当态,期待进程调剂算法来调剂该进程履行…… 本次尝试包括以下内容:
其中:
三个字段之间用制表符分隔。例如: 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 下, image-20210903111253440 在 Ubuntu 下, 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); 点窜此模板,用 它可以用来检验有关 log 文件的点窜能否正确,同时还是数据统计工作的根本。
关键函数诠释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挪用的一个奇妙之处就是它仅仅被挪用一次,却可以返回两次,它能够有三种分歧的返回值: 在fork函数履行终了后,假如建立新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新建立子进程的进程ID。我们可以经过fork返回的值来判定当进步程是子进程还是父进程。 援用一位网友的话来诠释fpid的值为什么在父子进程平分歧。“实在就相当于链表,进程构成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 由于子进程没有子进程,所以其fpid为0. img 有人说两个进程的内容完全一样啊,怎样打印的成果纷歧样啊,那是由于判定条件的缘由,上面罗列的只是进程的代码和指令,还有变量啊。 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
在 time.h 文件中,还界说了一个常量 下文就模拟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(¤t_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 文件的点窜能否正确,同时还是数据统计工作的根本?
尽早翻开log文件操纵系统启动后先要翻开 为了能尽夙起头记录,该当在内核启动时就翻开 log 文件。内核的进口是 move_to_user_mode(); if (!fork()) { /* we count on this going ok */ init(); } //…… 这段代码在进程 0 中运转,先切换到用户形式,然后全系统第一次挪用 在 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 中就完成。所以把这一段代码从 点窜后的 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 中建立了。按照 |
近日深度操作系统官方宣布,国产操作系统deepin 20.6版本正式上线,新版本升级了Stabl
【键盘操作方法大全】键盘可不仅仅能帮我们打字哦,还有很多快捷的操作你都知道吗?除
关于电脑的一些基本常识和操作(电脑初学者必备) 众所周知,在21世纪的今天,电脑
知乎Markdown适配不行,希望在我的博客中查看文章作者寄语操作系统实验的学习是一个循
前言只有光头才能变强这个学期开了Linux的课程了,授课的老师也是比较负责任的一位。
大家好,我是你们的新朋友叨叨张,很高兴能够在这里和大家相遇,今天我要分享的主题是
大家好我是正经人你以为上来就要教封面上那个效果吗?当然不是,那个是我好几年前做的
操作系统的数十年沉浮1946年诞生第一台计算机时,还没有操作系统。程序员靠着「打孔」
在编程届有个共识,想要成为一个合格的程序员必须要掌握 GitHub 的用法!接下来,我们
(预警:因为详细,所以行文有些长,新手边看边操作效果出乎你的预料)一:Git是什么
前言介绍快捷键,也就是刷刷按几下键盘上的组合键就可以达到鼠标点很多下才能实现的效
我在上篇文章说过,上海医保需要社保(即养老保险)成功转入杭州后才能进行转移,申请
高中化学实验真复杂,包学习APP为你整理最全总结,不怕记不住!一、中学化学实验操作
最近收到不少读者留言,关于怎么学「操作系统」和「计算机网络」的留言,小林写这一块
文/小渔俗话说:“好记性不如烂笔头。”在无纸时代,记笔记当然也不一定要用烂笔头了
之前安利过不少值得安装或使用的软件,但这一次我想换个角度,写一些强烈不建议安装的
服务器地址:http://kms.03k.org(点击检查是否可用);服务作用:在线激活windows和off
从Windows8开始,Windows的开机速度有了极大的提高,这得益于一项新的功能:快速启动
国产流氓软件之所以流氓就流氓在 “ 买一赠N ”装一个软件,就会给你附赠N个流氓软件
推荐10个超好玩的网站,窥探别人的记忆,敲键盘听歌,办公偷懒神器,看中国古今妖怪…
声明:本站内容由网友分享或转载自互联网公开发布的内容,如有侵权请反馈到邮箱 1415941@qq.com,我们会在3个工作日内删除,加急删除请添加站长微信:15924191378
Copyright @ 2022-2024 私域运营网 https://www.yunliebian.com/siyu/ Powered by Discuz! 浙ICP备19021937号-4