ハルビン工業大学オペレーティングシステム実験概要

現在、実験用アドレス https://www.lanqiao.cn/courses/115/learning/?id=374で実験が行われています。利点は、環境が事前に構成されていることです。欠点は、コードを入力するときにネットワーク遅延があり、環境を保存できないことです。一般的には、この方が便利です。

教科書、《Linux カーネル完全注釈付き改訂版 V5.0》 (Zhao Jiong)、以下、<<ノート>、<<アセンブリ言語> (Wang Shuang)

 

実験 1 実験環境に慣れる

この実験では、ホストマシンに ubuntu を使用し、boch エミュレータを介して Linux0.11 の動作をシミュレートする実験環境の基本的な状況を主に紹介します。

主に以下のコマンドが使用されます。

cd /home/shiyanlou/oslab/  #cd 是change directory 表示更换文件夹, 后面跟的是更改的文件地址, 如果不是以斜杠 '/'开头的话默认是当前文件夹, 和'./'表示相同的意思, '/'开头表示根目录

tar -zxvf hit-oslab.tar.gz -C /home/shiyanlou  #tar是打包命令, 在"鸟哥的linux私房菜"里有详细的介绍, 这里的-z表示用zip格式压缩或则解压, -x表示extract就是解压, -v表示显示压缩或者解压的文件细节, -f后面跟处理的tar文件(无论是压缩还是解压), -C后面跟的是指定的解压地址, C为大写

make all  #这里的make是一个编译命令, 当前所在文件夹必须有Makefile文件, 关于Makefile文件在<<linux内核完全注释(第五版)>>里3.6节有详细的介绍, 这个知识个人认为非常有必要掌握

sudo ./mount-hdc  # 挂载hdc硬盘命令, 必须使用sudo (super user do)来完成, 否则权限不够; 并且当前目录下的hdc文件夹的主要文件映射是在 /hdc/usr/root/ -> /root; ./run进入boch模拟之后的默认其实目录是/home/usr/root, 所以为了交换文件方便, 尽量放在/hdc/usr/root里

sudo umount hdc 跟上一个命令对应, 在./run运行boch前要umount

 

実験 2 オペレーティング システムの起動

この実験では主に、.s ファイル (つまり、アセンブリ ファイル) の変更を伴うオペレーティング システムの電源投入から起動までの全プロセスを紹介します。そのため、アセンブリについてある程度の理解が必要です。これを理解するには、Wang Shuang の <<アセンブリ言語>> (主にリアル モードでのアセンブリ操作を紹介します) を使用できます。また、<<ノート>> の 3 章では、AT&T 構文と 8086 リアルモード アセンブリ構文の違い、および C 言語の組み込みアセンブリの形式について説明します。 . 言葉が理解できません

以下は、実験プロセスに関する私自身の質問と考えの一部です。

#6.2完成bootsect.s的屏幕输出功能
#.org 510    这一句的意思是下面的代码从第510个字节开始算, 后面跟了一个.word 0xAA55, 正好印证了引导扇区是512个字节
#.word 0xAA55    这一句是约定的引导扇区的地址, 没有别的意思

dd bs=1 if=bootsect of=Image skip=32     #这里的dd是linux的备份命令, 查了一下是disk destroyer的缩写, 在<<鸟哥>>里第九章有介绍, bs表示block size 一个块的大小, if 是 input file, of是output file, skip 32 表示跳过前32个字节, 这句命令在linux0.11目录下的Makefile里也有, 所以手工编译链接的时候需要你进行这一步处理

jnc     # 汇编命令, jump no carry, 没有进位时跳转
jmpi    # 段间跳转命令, 所以后面必须有两个操作数, 一个表示段, 一个表示段内偏移
lds si, memory    # load ds, 从memory中获取32位的信息到 ds:si中


 

この実験の重要なポイントは、オペレーティング システムの起動プロセス全体を理解することです。これについては、書籍 <<ノート>> の第 6 章で詳しく説明されています。私の個人的な感想の核心はこの図です。

実験3 システムコール

この実験では、ユーザーモードからカーネルモードに移行してカーネルコードを呼び出す方法と、カーネルコードを修正および追加する方法を紹介します。

システム コールを実装するためのオペレーティング システムの 5 つの段落理論は、例と組み合わせると次のように表現されます。

1.应用程序调用库函数(API);
2.API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
3.内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
4.系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
5.中断处理函数返回到 API 中;
API 将 EAX 返回给应用程序

# 以系统调用 close.c 为例
第一步, 应用程序调用 int close(int fd)     # 这里是用户态, 用户调用close()函数是系统调用的API

第二步, API将系统调用号NR_close存到eax里作为系统中断int 0x80的第一个参数, 根据参数调用_NR_table里对应的系统调用        # 这里是通过_syscall1(int, close, int , fd)这个宏定义将close变成__NR_close, __NR_close在文件sys.h中定义了其序号以及相应的处理函数, 这个时候刚进入内核状态, 后面进行内核栈的切换操作, 切换操作就是让 ds, es 指向核心地址空间 0x10, 用户地址空间是0x17

第三步, 查sys_call_table, 调用对应的内核函数sys_close        #  这时候已经进入内核态, 并进行内核栈的切换
第四步, sys_close在内核态处理, 返回的结果在eax中
第五步, 内核态切换为用户态, 此时返回值是用户态的返回值

この実験にはインターネット上に小さなバグがたくさんあります。iam.c で値を割り当てる前に、バッファの値を 0 に設定する必要があります。そうしないと、最後の設定の痕跡が残ります。

 

実験4 プロセスの走行軌跡の追跡と統計

この実験は、オペレーティング システムの中核部分、マルチプロセス プログラミングに入り始めました。

実験に関係した文書と個人的な理解は次のとおりです。

process.c     # 这是个样本程序, 实现了一个cpuio_bound函数, 用来模拟一个进程是cpu耗时为主还是IO耗时为主, 从而来观察得出一个进程的不同状态的持续时间

/var/process.log      # 通过在main.c的init()之前, 将文件描述符为3的输出关联到process.log, 因此后面用到fprintk就可以将输出指定在process.log里

(void) open("/var/process.log", O_CREAT | O_TRUNC | O_WRONLY, 0666)      # 这里的 O_CREAT是打开文件的flag设置, 定义在/usr/include/asm-generic/fcntl.h  O_CREAT 表示文件不存在就创建, O_TRUNC 表示文件存在就截为0(即清空), O_WRONLY 表示只写write only

/kernel/printk.c         # 这里面的门道有点多, 一个一个来

va_list args         # 这个是用来专门对应 const char* fmt 里的输出格式 , 如 "%d %s", ... 
struct file* file    # 这个struct file* 是在后面的文件系统里会学习到, 是一个文件类型的指针
struct m_inode* inode    # 这个inode是磁盘里的一个struct m_inode数据结构指针, 是一个文件节点, 里面存有当前文件对应的数据块block索引以及下一级目录的索引

count = vsprintf(logbuf, fmt, args)        # 这里是将fmt, args的结果输入到定义的内核缓存logbuf中, vsprintf返回输入的字节数, 这里的count表示输入了多少个字节到logbuf里

addl $8, %%esp\n\t        # 这里是栈的回滚, 前面pushl %logbuf 和 pushl %1正好占据了8个字节, 将esp加8就相当于抹去这两个数据, 后面的addl $12 也是相同的道理

/6.4jiffies 滴答
set_intr_gate(0x20, &timer_interrupt)        # 这里就是吧timer_interrupt这个时钟中断设置到IDT的0x20位置

/6.5寻找状态切换点
struct task_struct *p        # 这个struct task_struct 就是老师课件里提到的PCB(进程控制块 process control block), 里面有一个进程的各种信息, 栈, 寄存器, 上下文等, 定义在/include/linux/sched.h中

*p = current        # current是一个全局变量, 表示当前的进程控制块PCB  

実験で間違いやすいところは、

1. プロセス 0 は sys_pause を継続的に呼び出します。これに対処しない場合、Python ファイルはエラーを報告し、行を繰り返します。つまり、特定のプロセスの状態は前回と同じです。

2. switch_to(next) は次のタスクと現在のタスクの pid も比較します。同じタスクの場合、変更は必要ありません。

 

実験5 カーネルスタック切り替えによるプロセス切り替え

この実験は比較的難しく、主にコードの量が多く、カーネル スタックについてある程度の理解を必要とするため、注意しないと間違いを犯します。 

この実験の後、GDT テーブルの全体的な構造を覚えてください。

コードのコメントの一部は次のとおりです。

/6.3 schedule 与 switch_to
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)    #    (*p)->counter > c表示p的时间片比当前最大的时间片大, 所以当前p的优先级最高, 下一个调度的任务就是*p, 
    c = (*p)->counter, next = i, pnext = *p;        # next 是下一个任务的编号, 所有的任务是定义的一个NR_TASKS

//.......

switch_to(pnext, LDT(next));
/6.4 实现switch_to
pushl %ebp
movl %esp, %ebp        # 这两句是栈帧操作, %ebp保存当前栈的栈帧位置, 因为栈是往下扩展的, 一个栈的栈帧是地址的最高值

movl 8(%ebp), %ebx        # %ebp是当前栈帧的地址, %ebp+4是保存的上一个栈帧的地址, %ebp+8就是switch_to的第一个参数, 回想一下上一节switch_to(pnext, LDT(next)), 因此这里是把pnext的值赋给了%ebx, 也就是要切换的下一个任务的指针

je 1f        # 这里的1是一个标号, 对应的下面的1:    , f表示forward,向前跳转到1的位置

movl tss, %ecx
addl $4096, %ebx    # %ebx表示下一个任务的指针, 增加4096相当于开辟了4096个字节的空间, 也就是一页的大小, 可以作为内核PCB的空间
movl %ebx, ESP0(%ecx)    # 这里就把%ebx也就是任务的指针存到了任务0的内核栈指针那里, 所有的内核任务都共用这一个地址

movl %esp,KERNEL_STACK(%eax)    #    这个%eax其实也是pnext指针, KERNEL_STACK(%eax)就是下一个任务的内核栈地址

また、理解する必要があるのは、タスク状態セグメント TSS (タスク状態セグメント) の構造です。

PCB の構造 (task_struct) と同様に、ソース コードでそれを確認するか、非常に詳細な別のブロガーのブログ投稿を読むことができます。

https://blog.csdn.net/qq_29503203/article/details/54618275

 

実験6 セマフォの実装と応用

この実験は、主にセマフォを用いてプロセス間通信とロックの原理を説明するもので、大学院受験や就職活動などに比較的役立つと思われる実験です。

まず第一に, sem_t データ構造を設計する必要があります. ビデオによると, セマフォ構造体には 2 つの基本メンバーが必要です, 1 つはセマフォの名前 (char* 名)、もう 1 つはセマフォ内の値 (int 値) です. プロセス間の通信はブロックされ、このセマフォを通じて起動されます。

タスク キューもあり、sem_t_queue へのポインタをセマフォ sem_t に追加したり、実験ガイドの暗黙的キューを直接使用したりできます。

第 2 に、プロデューサー/コンシューマー プログラムの PV 順序を理解する必要があります。ミューテックス セマフォ MUTEX (クリティカル セクション) では、他のセマフォ リソースの使用をできるだけ少なくする必要があります。使用量が増えるほど、デッドロックが発生しやすくなります。実験的思考の問題は、この問題の例です。 

/6.1 信号量
Producer()
{
    // 生产一个产品 item;

    // 空闲缓存资源
    P(Empty);

    // 互斥信号量
    P(Mutex);

    // 将item放到空闲缓存中;
    V(Mutex);

    // 产品资源
    V(Full);
}

Consumer()
{
    P(Full);
    P(Mutex);

    //从缓存区取出一个赋值给item;
    V(Mutex);

    // 消费产品item;
    V(Empty);
}
/6.2 多进程共享文件

使用标准 C 的文件操作函数要注意,它们使用的是进程空间内的文件缓冲区,父进程和子进程之间不共享这个缓冲区        # 这句话其实隐含的说明了进程的内存空间是独立的, 父进程和子进程之间不会共享, 所以需要写完之后马上fflush一下, 手动把要写入的东西立即写入磁盘

建议直接使用系统调用进行文件操作    # 这句话说明系统调用可以使用统一的内核空间, 这样相当于直接使用了共享内存

/6.4 原子操作, 睡眠和唤醒

这里用到的原子操作是开关中断(sti(), cli()), 缺点是只能在单核上有效, 因为开关中断是CPU单核上的一个引脚的置1或者0
课堂上还讲了利用硬件的原子操作test&set(sem), 也就是P,V操作

strcmp は、セマフォ名を比較するために string.h で使用されます (sem_t ->name)

 

実験 7 アドレスのマッピングと共有

この実験の最初の部分は、論理アドレスが層ごとに物理メモリにどのようにマッピングされるかを視覚的に示すことです。

論理アドレス上のdsはセグメントセレクター(セグメントセレクター)で、LDTの上のオフセットで、LDTを見つけたいならGDTを見つけないといけないので、GDTR(レジスタ)を調べる→GDTのアドレスを見つける→LDTR(レジスタ)に対応する桁数を調べる→GDTアドレスからLDTのオフセットを見つける→GDTオフセットの64ビットデータを見つけて3を取り出す、という流れになります。 LDT の物理アドレスを含む 2 ビットの情報、つまり実際のセグメント ベース アドレス --> セグメント ベース アドレス + セグメント オフセット 3004 に従って、論理アドレス ds:3004 の物理アドレスを取得できます。

セグメント セレクター ds の値は実際には非常に特殊です。主に 0x17 のバイナリ値が 00010111 であり、最後の 2 桁が特権レベルを示すため、ds=0x17 はユーザー モードを意味すると言われています。ここで、11 は 3 を意味し、これはたまたまユーザー モード特権レベル 3 です。2 番目のビットは TI を意味します。TI は 1、つまり LDT でデータをチェックすることを意味するため、ユーザー モードがデータをチェックするためにたまたま LDT に移動するだけです。カーネル モードはデータをチェックするために GDT に行きます。したがって、0x10 はカーネル モードが意味があることを意味します。はい、0x10 のバイナリ値は 00010000 で、最後の 2 桁は特権レベルが 0 であることを示します。カーネル モードの特権レベルです。TI ビットは 0 です。これは、データが GDT 上でチェックされていることを意味します。これは、カーネル モードのデータが GDT 上でチェックされていることを確認します。

リニア アドレスを通じて物理アドレスを見つけるのは簡単です。ページ テーブルの知識を使用すると、リニア アドレスの 32 ビットは、ページ ディレクトリ + ページ テーブル エントリ + ページ オフセットに分割されます。ページ ディレクトリ エントリとページ テーブル エントリのサイズは 32 ビットの数値であるため、対応するページ テーブルは、ページ ディレクトリのベース アドレス + ページ ディレクトリ番号 * 4 に従って見つけられ、ページ テーブルのベース アドレス + ページ テーブル番号 * 4 によってページ番号が取得され、最後に + ページ オフセットによって物理アドレスが取得されます。

2 番目の実験の核となる部分は、実際には、空き物理ページを見つけ、その物理ページをカーネルの共有スペース テーブルに保存し、共有メモリの目的を達成するために、2 つのプロセスが shmid に従って同じ共有カーネル メモリを取得できるようにすることです。実験の残りの部分は実験 6 と同様です。ここでは、文字 & の使用方法と、プロセス スペース内の 64M メモリがどのように分散されるかを学びました。

 

実験8 端末装置の制御

この実験は主にキーボード入力とディスプレイ出力の扱い方を学ぶことを目的としています。F12 を押してすべての文字を '*' にするためには、'*' の出力がどこにあるのかを調べる必要があります。ビデオでの教師の紹介によれば、ファイル console.c を変更する必要があります。

また、対応する F12 の入力コードを調べて、入力バッファ内の F12 の入力コード (ESC、[、[、L のようです) を判断する必要があります。一度出現すると、console_write 関数でコンソールに書き込まれる文字は「*」になります。この方法により、F12 を押すだけで「*」に変更できるようになります。

このブロガーの実践を参照してくださいhttps://blog.csdn.net/weixin_45666853/article/details/105278345

 

実験9 PROCファイルシステムの実装

この実験は主に、ファイルシステムにおけるブロックと i ノードの概念を学習し、対応する i ノードをマウントすることで仮想ファイル PROC を実現することを目的としています。 

主なことは、ノードを作成するための 2 つの関数 mkdir() と mknod() の使用法を理解し、次に各 pid に対応する状態、start_time、count およびその他の情報を取得するための task_struct の構造を知ることです。

hdinfo の取得方法はまだ少し不明瞭ですが、全体としては、使用されているブロックと i ノードをすべてトラバーサルでカウントし、ブロックと i ノードの合計を使用して減算します。

残りのステップを理解するのは特に難しいことではなく、実験のガイダンスに従って段階的に結果を得ることができます。

 

要約する

9 つの実験にはほぼ 1 か月かかり、月の前半は Wang Shuang の <<アセンブリ言語>> に費やしました。私はリアルモードのアセンブリ言語についてはある程度理解しています。しかし、実際には、このアセンブリに関する一連の実験の要件は、書籍 <<ノート>> に記載されています。実際、より速く完了できます。完了プロセス中に他の多くの人の方法も参考にしました。初めてこれに触れたとき、私はまだ途方に暮れていました。独立して大きなコードを書くことができます唯一の頭痛の種はデバッグであり、GDB でバグを見つけるのは非常に困難です。

 

おすすめ

転載: blog.csdn.net/Mint2yx4/article/details/113740871