目次
(2) 共有メモリに基づくプロデューサー・コンシューマー・プログラム
(3) 共有メモリに基づくプロデューサー・コンシューマー・プログラム
1. 実験の目的
1. オペレーティング システムのセグメントおよびページ メモリ管理についての深い理解、およびセグメント テーブル、ページ テーブル、論理アドレス、リニア アドレス、物理アドレスなどの概念についての深い理解。
2. セグメントおよびページ メモリ管理のアドレス マッピング プロセスを練習します。
3. オペレーティングシステムのメモリ管理を深く理解するために、セグメントおよびページメモリ管理におけるメモリ共有を実現するプログラム。
2. 実験内容
(1) アドレス変換プロセスの追跡
Bochs デバッグ ツールを使用して、Linux 0.11 のアドレス変換 (アドレス マッピング) プロセスをトレースし、IA-32 および Linux 0.11 のメモリ管理メカニズムを理解します。
まず、アセンブリ レベルのデバッグで Bochs を起動し、Linux 0.11 を起動し、0.11 でコンパイルして実行します(これは無限ループ プログラムであり、アクティブに終了することはありません)。次に、デバッガでさまざまなシステム パラメータを表示し、 論理アドレス、LDT テーブル、GDT テーブル、リニア アドレスからページ テーブルまで変数の物理アドレスを計算します。最後に、物理メモリを直接変更して出口を実行します。
test.c
i
test.c
【テスト.c】
#include <stdio.h>
int i = 0x12345678;
int main(void)
{
printf("The logical/virtual address of i is 0x%08x", &i);
fflush(stdout);
while (i)
;
return 0;
}
(2) 共有メモリに基づくプロデューサー・コンシューマー・プログラム
共有メモリをバッファとして使用して、Ubuntu 上でマルチプロセスのプロデューサー/コンシューマー プログラムを作成する
この実験は Ubuntu で完了しており、セマフォ実験の 機能要件は基本的に同じですが、相違点は 2 つだけです。pc.c
- ファイルをバッファとして使用する代わりに、共有メモリを使用します。
- プロデューサーとコンシューマーは別個のプログラムです。生産者は、消費者は です。どちらのプログラムも単一プロセスであり、セマフォとバッファを介して通信します。
producer.c
consumer.c
Linux では、 2 つの システム コールで共有メモリを使用できます 。 shmget()
shmat()
(3) 共有メモリの実現
セマフォの実験に基づいて、共有メモリ機能が Linux 0.11 に追加され、プロデューサー/コンシューマー プログラムが Linux 0.11 に移植されました。
プロセスはページ共有を通じて通信できます。共有ページは共有メモリと呼ばれます。その構造は次の図に示されています。
このパートの実験内容は、上記のページ共有を Linux 0.11 上で実現し、以前の実装を移植して producer.c
ページ 共有の有効性を検証することです。consumer.c
【特定の要件】
と の mm/shm.c
2 つのシステム コールを実装する場合 、と の動作を サポートするだけでよく 、POSIX で規定されている機能を完全に実装する必要はありません。shmget()
shmat()
producer.c
consumer.c
shmget()
int shmget(key_t key, size_t size, int shmflg);
関数 | メモリのページを作成/開き、ページの共有メモリの shmid (オペレーティング システムの共有メモリの ID) を返します。 |
パラメータ | 同じ共有メモリを使用するすべてのプロセスは、同じキーパラメータを使用する必要があります shmflg パラメータは無視できます。 |
戻り値 | キーに対応する共有メモリが確立されている場合は、shmidを直接返す サイズがメモリの 1 ページのサイズを超える場合は、-1 を返し、errno を EINVAL に設定します。 システムに空きメモリがない場合は、-1 を返し、errno を ENOMEM に設定します。 |
shmat()
void *shmat(int shmid, const void *shmaddr, int shmflg);
関数 | shmidで指定された共有ページを現在のプロセスの仮想アドレス空間にマッピングし、その最初のアドレスを返します。 |
パラメータ | shmid: オペレーティング システム内のブロック共有メモリの ID shmaddr および shmflg パラメータは無視できます |
戻り値 | shmidが無効な場合は、-1 を返し、errno を EINVAL に設定します。 |
3. 実験の準備
1. Linux の共有メモリ
Linux は 2 種類の共有メモリをサポートしています。1 つの方法は と のshm_open()
組み合わせであり、もう 1 つの方法は と の組み合わせです。これらのシステムコールの詳細については、man および関連資料を参照してください。この実験では後者の方法を使用することを推奨します。mmap()
shm_unlink()
shmget()
shmat()
shmdt()
【特記事項】親子関係のないプロセス間で共有メモリ の第一パラメータを使用しないでください 。 共有できなくなります。どの数字を使用するかは気分によって異なります。shmget()
key
IPC_PRIVATE
2. 無料の物理ページを取得する
実験者はページ共有の実装方法を検討する必要があります。まず、Linux 0.11 がどのようにページを操作し、プロセスのアドレス空間を管理するかを見てください。
中には kernel/fork.c
以下のものがあります:
int copy_process(…)
{
struct task_struct *p;
p = (struct task_struct *) get_free_page();
if (!p)
return -EAGAIN;
// ……
}
これは get_free_page()
、空き物理ページを取得するために使用されます mm/memory.c
。
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");
__asm__("std ; repne ; scasb\n\t"
"jne 1f\n\t"
"movb $1,1(%%edi)\n\t"
// 页面数*4KB=相对页面起始地址
"sall $12,%%ecx\n\t"
// 在加上低端的内存地址,得到的是物理起始地址
"addl %2,%%ecx\n\t"
"movl %%ecx,%%edx\n\t"
"movl $1024,%%ecx\n\t"
"leal 4092(%%edx),%%edi\n\t"
"rep ; stosl\n\t"
//edx赋给eax,eax返回了物理起始地址
"movl %%edx,%%eax\n"
"1:" :"=a" (__res) :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
"D" (mem_map+PAGING_PAGES-1):"di","cx","dx");
return __res;
}
static unsigned char mem_map [ PAGING_PAGES ] = {0,};
明らかに、 get_free_page
この関数はビットマップ内で値 0 (空きページ) を持つ項目を検索し、ページの開始物理アドレスを返すこと mem_map
です。
3. アドレスマッピング
空き物理ページを使用すると、次のステップは、リニア アドレスと物理ページ間のマッピングを完了することです。Linux 0.11 にもそのようなコードがあります。見てください。この関数は、物理ページに対応する物理ページが存在しない状況に対処するために使用さ れ ます mm/memory.c
。 リニア アドレスが無効である (つまり、ページ割り込みが欠落している) 場合、次のような 重要な関数が function 内で呼び出されます 。do_no_page(unsigned long address)
address
do_no_page
get_empty_page(address)
// 函数 get_empty_page(address)
……
unsigned long tmp=get_free_page();
// 建立线性地址和物理地址的映射
put_page(tmp, address);
……
これら 2 つのステートメントは、空き物理ページを取得し、リニア アドレス address
に対応するページ ディレクトリとページ テーブルを埋めるために使用されます。
4. 空き仮想アドレス空間を見つける
空き物理ページでは、リニア アドレスと物理ページ間のマッピングもありますが、この実験を完了するには、空き仮想アドレス空間を取得する必要があります。
データ セグメントから空間のセクションを描画するには、まずプロセス データ セグメントの空間の分布を理解する必要があります。この分布は明らかにシステム コールによって決定されるため、コア コード ( の)を詳しく見てください。 exec
exec
do_execve
fs/exec.c
関数内で do_execve()
データセグメントを変更する(もちろんLDTを変更する)箇所は、 change_ldt
関数がchange_ldt
次のように実装されています。
static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
{
/* 其中text_size是代码段长度,从可执行文件的头部取出,page为参数和环境页 */
unsigned long code_limit,data_limit,code_base,data_base;
int i;
code_limit = text_size+PAGE_SIZE -1;
code_limit &= 0xFFFFF000;
//code_limit为代码段限长=text_size对应的页数(向上取整)
data_limit = 0x4000000; //数据段限长64MB
code_base = get_base(current->ldt[1]);
data_base = code_base;
// 数据段基址 = 代码段基址
set_base(current->ldt[1],code_base);
set_limit(current->ldt[1],code_limit);
set_base(current->ldt[2],data_base);
set_limit(current->ldt[2],data_limit);
__asm__("pushl $0x17\n\tpop %%fs":: );
// 从数据段的末尾开始
data_base += data_limit;
// 向前处理
for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {
// 一次处理一页
data_base -= PAGE_SIZE;
// 建立线性地址到物理页的映射
if (page[i]) put_page(page[i],data_base);
}
// 返回段界限
return data_limit;
}
change_ldt
関数を注意深く分析し 、データ セグメントから空きリニア アドレスを見つける方法を分析します。
4. 実験プロセス
(1) アドレス変換プロセスの追跡
0. test.c を書き込みます。
これを Ubuntu の下に書き込み 、Linux 0.11 システムにコピーします。test.c
// oslab 目录下
sudo ./mount-hdc
cp ./exp_07/test.c ./hdc/usr/root/
sudo umount hdc/
1. 準備する
Linux 0.11 をコンパイルした後、まず次のコマンドを実行してデバッガを起動します。
./dbg-asm
現時点では、Bochs ウィンドウは黒い画面状態になっています。
この時点で、端末には次のように表示されます。
これは、次のコマンドが Bochs の起動後に実行される最初のソフトウェア コマンドであることを示しています。この時点で、ステップインすることで BIOS コードを確認できますが、これはこの実験では必要ありません。 Next at t=0
ここで、コマンド c
(つまり continue) を直接入力してプログラムの実行を続行すると、Bochs は通常どおり Linux 0.11 を起動します。
Linux 0.11 が正常に起動しました。
ここで、Linux 0.11 でコンパイルして実行します 。test.c
変更されていない限り、 プログラムが同じマシン上で複数回実行されたとしても、(論理/仮想アドレス) 値はどのマシンでも同じです。test.c
0x00003004
test
さらに、プログラムは無限ループであり、継続的に CPU を占有するだけで終了しません。 test
2.一時停止
test
プログラムの実行 中に 、ターミナルのコマンド ライン ウィンドウで を押すとCtrl + c
、Bochs が一時停止し、デバッグ状態に入ります。
ほとんどの場合、 test
プログラムが一時停止され、次のような情報が表示されます。
(0) [0x00fc8031] 000f:00000031 (unk. ctxt): cmp dword ptr ds:0x3004, 0x00000000 ; 833d0430000000
- それらの 1 つが
000f
の 場合0008
、割り込みがカーネル内にあることを意味します。このとき、 継続して実行し、 になるまで一時停止する必要があります 。c
Ctrl+c
000f
- 表示される次のコマンドがそうでない場合( ステートメントの先頭を参照 )、コマンドを使用して、停止するまで 1 つのステップでいくつかのステップを実行します 。
cmp ...
cmp
n
cmp ...
次に、コマンドを使用して、 u /8
現在位置から始まる 8 命令の逆アセンブリ コードを表示します。構造は次のとおりです。
上記8命令はwhileからreturnまでのアセンブリコードです。このうち変数iはこのアドレスに格納されており 、0と比較し続け、0になるまでループから抜け出します。 test.c
ds:0x3004
ds:0x3004
次に、論理アドレスに対応する物理アドレスを探し始めます 。
3. セグメントテーブル
ds:0x3004
ds
このアドレスがds
セグメント0x3004
に属していることを示す 仮想アドレスであり 、セグメント内のオフセットです。まずセグメント テーブルを見つけ、次に のds
値を使用してセグメント テーブル内のセグメントの特定の情報を見つけてds
から、アドレス変換を続行する必要があります。
IA-32 (Intel Architecture 32-bit) 上で動作するすべてのアプリケーションプログラムには、 LDT (Local Descriptor Table) と呼ばれるセグメントテーブルがあり、セグメント情報はセグメントディスクリプタと呼ばれます。
では、LDT はどこにあるのでしょうか。ldtr レジスタが手がかりの開始点であり、これを通じて GDT (グローバル記述子テーブル) で LDT の物理アドレスを見つけることができます。
コマンド (デバッグ ウィンドウで入力)を使用して sreg
各レジスタの値を表示します。
ldtr の値は0x0068で、バイナリに変換すると000000000 1101 000であることがわかります。これは、LDT テーブルがGDT テーブルの1101 (バイナリ) = 13 (10 進数)の位置に格納されていることを意味します (意味は次のとおりです)。データの各ビットについては、後述のセグメント セレクターを参照してください)。GDT テーブルの場所は、gdtr、つまり物理アドレスによって指定されます 0x00005cb8
。
次のように、物理アドレスから始まるxp /32w 0x00005cb8
表示するために使用します 。 0x00005cb8
GDT テーブルの各項目は 64 ビット (8 バイト、つまり 2 ワード) を占め、LDT テーブルは GDT テーブルの 13 番目に格納されていることがわかっているため、検索対象の項目のアドレス は です 0x00005cb8+13*8
。
と入力し xp /2w 0x00005cb8+13*8
、以下を取得します。
上記 2 つの値は、ここでのスクリーンショットの値と一致しない場合がありますが、これは正常です。それが正しいかどうかを確認したい場合は、前の 出力 で ldtr が配置されている行の sum の値を確認してくださいsreg
。 それらは Bochs デバッガーによって自動的に計算されます。見つかった値はそれらと一致している必要があります。そうでない場合は、間違った位置を見つけなければなりません。dl
dh
ここで、 0x 92d0 0068 と 0x000082 fd の太字の数字を組み合わせると 0x00fd92d0
、これが LDT テーブルの物理アドレスになります (この組み合わせの理由については、後で紹介するセグメント記述子を参照してください)。
xp /8w 0x00fd92d0
、LDT テーブルの最初の 8 文字の内容を取得します。
上記は LDT テーブルの最初の 4 項目です (1 項目は 2 文字を占めます)。
4. セグメント記述子
プロテクト モードでは、セグメント レジスタには セグメント セレクタという別の名前が付けられます。これは、セグメント レジスタが保持する情報は主にセグメント テーブル内のセグメントのインデックス値であり、セグメント テーブル記述子から対応するセグメントを「選択」するために使用できるためです。
まずds
セレクターの内容を確認するか、次の sreg
コマンドを使用します。
の値が であり 、セグメント セレクターが 16 ビットのレジスタであることが わかります。各ビットの意味は次のとおりです。ds
0x0017
ds
- RPLはリクエスト特権レベルです。セグメントにアクセスするとき、プログラムにアクセスするのに十分な特権レベル (CPL) がある場合でも、プロセッサはRPL と CPL (現在のコードの特権レベルを示すためにビット 0 とビット 1 に配置されます) をチェックします。
cs
ただし、ds
RPL (リクエスト データ セグメントを示す中央に配置) の特権レベルが不十分な場合、つまり、RPL の値が CPL より大きい場合 (値が大きいほど、セグメントにアクセスできません)。値、権限が小さいほど)、RPL の値を使用して、CPL の値をオーバーライドします。 - TIはテーブルインジケータで、TI=0の場合はセグメントディスクリプタ(セグメントの詳細情報)がGDT(グローバルディスクリプタテーブル)にあること、つまりGDTに行って確認することを意味し、TI=1の場合はGDT(グローバルディスクリプタテーブル)にあることを意味します。 LDT (ローカル記述文字テーブル) を確認します。
上記をもう一度見てみましょうds
。 0x0017 = 0000000000010 1 11 (バイナリ) したがって、 RPL= 11、これは最も低い特権レベルにあることがわかります (アプリケーション プログラムで実行されるため)、 TI= 1は、次のことを意味します。 LDT テーブルを検索すると、インデックス値は10 (2 進数) = 2 (10 進数) です。これは、LDT テーブル内で番号 2 のセグメント記述子を見つけることを意味します (番号は 0 から始まるため、3 番目の項目になります)。
LDT と GDT の構造は同じで、各エントリは 8 バイトを占有するため、LDT テーブルの 3 番目の項目は、 0x00003fff 0x10c0f300
長時間検索されたセグメント記述子になりますds
。
出力内の行のと の値によって、正しい記述子が見つかったことを確認できます。 sreg
ds
dl
dh
次に、セグメント記述子に何が配置されているかを確認します。
セグメント記述子は 64 ビットの 2 進数であり、セグメント ベース アドレスやセグメント長などの重要なデータが格納されていることがわかります。
- ビット P (存在) はセグメントが存在するかどうかを示すフラグです
- ビット S は、システム セグメント記述子 (S=0) であるか、コードまたはデータ セグメント記述子 (S=1) であるかを示すために使用されます。
- 4 ビットの TYPE は、データ セグメント、コード セグメント、読み取り可能、書き込み可能などのセグメントのタイプを示すために使用されます。
- DPLはセグメントの権限であり、CPLやRPLに相当します。
- ビット G は粒度です。G=0 はセグメント制限がビット単位であることを意味し、G=1 はセグメント制限が 4KB 単位であることを意味します
その他の内容については詳しく説明しません。
5. セグメントベースアドレスとリニアアドレス
多くの労力を費やした結果、実際に必要なのはセグメント ベース アドレス、つまりセグメント記述子の 3 つのベース アドレスだけです。セグメント記述子 と 太字部分(ベースアドレス)を合わせたものが、リニアアドレス空間におけるセグメントの開始アドレス となります。同じ方法を使用して、他のセグメントのベース アドレスも取得できます。これらはすべてこの番号です。0x00003fff
0x10c0f300
0x10000000
ds
セグメントベースアドレス + セグメントオフセット = リニアアドレス
したがって、ds:0x3004
の線形アドレスは次のようになります。
0x10000000 + 0x3004 = 0x10003004
calc ds:0x3004
この結果は、次のコマンドで 確認できます。
6. ページテーブル
リニア アドレスから物理アドレスまで、ページ テーブルを検索する必要があります。
リニア アドレスを物理アドレスに変換するプロセスは次のとおりです。
まず、リニア アドレス内のページ ディレクトリ番号、ページ テーブル番号、およびページ オフセットを計算する必要があります。これらは、それぞれ 32 ビット リニア アドレスの 10 ビット + 10 ビット + 12 ビットに対応します。
したがって、リニア アドレス 0x10003004 = 1000000 0000000011 000000000100 (バイナリ)のページ ディレクトリ番号は64 ( 1000000 )、ページ テーブル番号は 3 ( 0000000011 )、ページ オフセットは 4 ( 000000000100 ) です。
IA-32 では、ページ ディレクトリ テーブルの場所はCR3レジスタによって決まり、creg
次のコマンドで確認できます。
xp /68w 0
ページ ディレクトリテーブルの最初の 68 文字を見て、ページ ディレクトリ テーブルのベース アドレスが 0 であることを説明します 。
ページ ディレクトリ テーブルとページ テーブルの内容は非常に単純で、1024 個の 32 ビット数値 (正確には 4K) です。32 ビットのうち、最初の 20 ビットは物理ページ フレーム番号で、その後にいくつかの属性情報が続きます (最後のビット P が最も重要です)。このうち、ページ ディレクトリ番号 64 は、65 番目のページ ディレクトリ アイテム (0 から番号付け) であり、次のことを確認することで探しているものになりますxp /w 0+64*4
。
上記は探しているページ ディレクトリ エントリであり、エントリの具体的な構造は次のとおりです。
ページ テーブルが配置されている物理ページ フレーム番号は であることがわかります 0x00fa7
。つまり、ページ テーブルは物理メモリに配置されているため 0x00fa7000
、3 番のページ テーブル エントリの検索を開始します (各ページ テーブル エントリは 4 です)。バイト、つまり 1 ワード) コマンドで表示xp /w 0x00fa7000+3*4
:
ページ ディレクトリ テーブルとページ テーブルのエントリ形式は同じであり、 対応する物理ページ フレーム番号である属性も同じです。 067
fa6
7. 物理アドレス
リニア アドレス0x10003004
に対応する物理ページ フレーム番号は であり0x00fa6
、0x004
ページ内のオフセットと接続して0x00fa6004
変数 i の物理アドレスを取得します。これは 2 つの方法で検証できます。
方法 1: コマンドを使用して page 0x10003004
、次の情報を取得できます。
0x10003000
リニアアドレスに対応する物理ページアドレスは であることがわかります0x00fa6000
。
方法 2: コマンドを使用して xp /w 0x00fa6004
、次の情報を取得できます。
この値は、test.c
の i の初期値です。
8. プログラムを終了する
i が 0 でない限りtest
プログラムは停止しないので、メモリを直接変更して i の値を 0 に変更してプログラムを終了します。 test
setpmem 0x00fa6004 4 0
上記のコマンドは、0x00fa6004
アドレスから始まる 4 バイトがすべて 0 に設定されることを示します。次にc コマンドを使用して Bochs の操作を続行し、 test
終了すれば i の変更が成功したことを意味し、実験は終了です。
test
プログラムは正常に終了しました。
(2) 共有メモリの実現
ここでの共有メモリの実装は、 と の2つのシステムコールを実現するものであり、と の動作を サポートできればよく 、POSIXで規定されている機能を完全に実装する必要はない。shmget()
shmat()
producer.c
consumer.c
1. システムコールを追加する
次に、システムコールの追加shmget
とshmat
システムコールの実験を参照していきます。
(1) システムコール番号を追加 - linux-0.11/include
/unistd.h
(2) システムコールの総数を変更する - linux-0.11/kernel/system_call.s
(3) システムコール関数名の追加とシステムコールテーブルの維持 - linux-0.11/include/linux/
sys.h
2. システムコールの実装
sys_shmget()
この関数の主な機能は、空き物理ページを取得することであり、これはget_free_page
既存のページを呼び出すことで実現できます。sys_shmat()
この関数の主な機能は、このページをプロセスの仮想アドレスおよび論理アドレスに関連付けることで、プロセスによる特定の論理アドレスの読み取りおよび書き込みがメモリ ページの読み取りおよび書き込みとなるようにします。この関数は、まず仮想アドレスと物理ページ間のマッピングを完了する必要がありますが、その中心となるのはページ テーブルを埋めることであり、これは既存のページ テーブルを呼び出すことで実現できますput_page
。
(1) shm.h を記述し、 関連するデータ型宣言と関数宣言を行うディレクトリ配下に新規ファイルを作成します
。include
shm.h
#include <stddef.h>
typedef unsigned int key_t;
struct struct_shmem
{
unsigned int size;
unsigned int key;
unsigned long page;
};
int shmget(key_t key, size_t size);
void* shmat(int shmid);
#define SHM_NUM 16
(2) shm.c を書き込む
これら 2 つの関数を実装するには、kernel
ディレクトリに新しいファイルを作成しますshm.c
。
#include <shm.h>
#include <linux/mm.h>
#include <unistd.h>
#include <errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
struct struct_shmem shm_list[SHM_NUM] = {
{0,0,0}};
int sys_shmget(key_t key, size_t size)
{
int i;
unsigned long page;
if(size > PAGE_SIZE)
{
errno = EINVAL;
printk("shmget:The size connot be greater than the PAGE_SIZE!\r\n");
return -1;
}
if(key == 0)
{
printk("shmget:key connot be 0!\r\n");
return -1;
}
//判斷是否已经创建
for(i = 0; i < SHM_NUM; i++)
{
if(shm_list[i].key == key)
return i;
}
page = get_free_page(); //申请内存页
if(!page)
{
errno = ENOMEM;
printk("shmget:connot get free page!\r\n");
return -1;
}
for(i = 0; i < SHM_NUM; i++)
{
if(shm_list[i].key == 0)
{
shm_list[i].size = size;
shm_list[i].key = key;
shm_list[i].page = page;
break;
}
}
return i;
}
void* sys_shmat(int shmid)
{
unsigned long tmp; //虚拟地址
unsigned long logicalAddr;
if(shmid < 0 || shmid >= SHM_NUM || shm_list[shmid].page == 0 || shm_list[shmid].key <= 0)
{
errno = EINVAL;
printk("shmat:The shmid id invalid!\r\n");
return NULL;
}
tmp = get_base(current->ldt[1]) + current->brk; //计算虚拟地址
put_page(shm_list[shmid].page,tmp);
logicalAddr = current->brk; //记录逻辑地址
current->brk += PAGE_SIZE; //更新brk指针
return (void *)logicalAddr;
}
(3) ファイルをマウントする
編集したもの shm.h
と変更した unistd.h
ものの両方を Linux 0.11 システムにコピーします。
// oslab 目录下
sudo ./mount-hdc
cp ./linux-0.11/include/unistd.h ./hdc/usr/include/
cp ./linux-0.11/include/shm.h ./hdc/usr/include/
sudo umount hdc/
(4) ファイルのコンパイルルールを変更する
linux-0.11/kernel
ディレクトリに 次の変更を加えます Makefile
。
shm.s shm.o: shm.c ../include/unistd.h ../include/linux/kernel.h \
../include/linux/sched.h ../include/linux/mm.h ../include/errno.h
(5) Linux 0.11を再コンパイルする
// linux-0.11 目录下
make all
(3) 共有メモリに基づくプロデューサー・コンシューマー・プログラム
プログラムのこの部分は基本的にセマフォ実験の 機能要件と同じですが、次の 2 つの違いのみがあります。pc.c
- ファイルをバッファとして使用する代わりに、共有メモリを使用します。
- プロデューサーとコンシューマーは別個のプログラムです。生産者は、消費者は です。どちらのプログラムも単一プロセスであり、セマフォとバッファを介して通信します。
producer.c
consumer.c
1.プロデューサー.c
/* producer.c */
#define __LIBRARY__
#include <unistd.h>
#include <linux/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/sched.h>
#include <linux/kernel.h>
_syscall2(sem_t *,sem_open,const char *,name,unsigned int,value)
_syscall1(int,sem_wait,sem_t *,sem)
_syscall1(int,sem_post,sem_t *,sem)
_syscall1(int,sem_unlink,const char *,name)
_syscall1(int, shmat, int, shmid);
_syscall2(int, shmget, unsigned int, key, size_t, size);
#define PRODUCE_NUM 200 /* 打出数字总数*/
#define BUFFER_SIZE 10 /* 缓冲区大小 */
#define SHM_KEY 2018
sem_t *Empty,*Full,*Mutex;
int main(int argc, char* argv[])
{
int i, shm_id, location=0;
int *p;
Empty = sem_open("Empty", BUFFER_SIZE);
Full = sem_open("Full", 0);
Mutex = sem_open("Mutex", 1);
if((shm_id = shmget(SHM_KEY, BUFFER_SIZE*sizeof(int))) < 0)
printf("shmget failed!");
if((p = (int * )shmat(shm_id)) < 0)
printf("shmat error!");
printf("producer start.\n");
fflush(stdout);
for(i=0; i<PRODUCE_NUM; i++)
{
sem_wait(Empty);
sem_wait(Mutex);
p[location] = i;
printf("pid %d:\tproducer produces item %d\n", getpid(), p[location]);
fflush(stdout);
sem_post(Mutex);
sem_post(Full);
location = (location+1) % BUFFER_SIZE;
}
printf("producer end.\n");
fflush(stdout);
/* 释放信号量 */
sem_unlink("Full");
sem_unlink("Empty");
sem_unlink("Mutex");
return 0;
}
2.cosumer.c
/* consumer.c */
#define __LIBRARY__
#include <unistd.h>
#include <linux/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/sched.h>
#include <linux/kernel.h>
_syscall2(sem_t *,sem_open,const char *,name,unsigned int,value)
_syscall1(int,sem_wait,sem_t *,sem)
_syscall1(int,sem_post,sem_t *,sem)
_syscall1(int,sem_unlink,const char *,name)
_syscall1(int, shmat, int, shmid);
_syscall2(int, shmget, unsigned int, key, size_t, size);
#define PRODUCE_NUM 200
#define BUFFER_SIZE 10
#define SHM_KEY 2018
sem_t *Empty,*Full,*Mutex;
int main(int argc, char* argv[])
{
int used = 0, shm_id,location = 0;
int *p;
Empty = sem_open("Empty", BUFFER_SIZE);
Full = sem_open("Full", 0);
Mutex = sem_open("Mutex", 1);
if((shm_id = shmget(SHM_KEY, BUFFER_SIZE*sizeof(int))) < 0)
printf("shmget failed!\n");
if((p = (int * )shmat(shm_id)) < 0)
printf("link error!\n");
printf("consumer start.\n");
fflush(stdout);
while(1)
{
sem_wait(Full);
sem_wait(Mutex);
printf("pid %d:\tconsumer consumes item %d\n", getpid(), p[location]);
fflush(stdout);
sem_post(Mutex);
sem_post(Empty);
location = (location+1) % BUFFER_SIZE;
if(++used == PRODUCE_NUM)
break;
}
printf("consumer end.\n");
fflush(stdout);
/* 释放信号量 */
sem_unlink("Mutex");
sem_unlink("Full");
sem_unlink("Empty");
return 0;
}
3. ファイルをマウントする
producer.c
と を consumer.c
Linux 0.11 システムにコピーします 。
sudo ./mount-hdc
cp ./exp_07/producer.c ./hdc/usr/root/
cp ./exp_07/consumer.c ./hdc/usr/root/
sudo umount hdc/
4. コンパイルして実行する
gcc -o pro producer.c
gcc -o con consumer.c
./pro > pro.txt &
./con > con.txt
コンパイル時の警告の問題は大きな問題ではありません。プログラムの実行結果を txt ファイルにリダイレクトして表示します。
同じ端末で 2 つのプログラムを同時に実行するにはどうすればよいですか?
Linux シェルには、バックグラウンドでプログラムを実行する機能があります。コマンドの最後に 1 を入力する限り &
、コマンドはバックグラウンドで実行され、フォアグラウンドはすぐにプロンプトに戻り、次のコマンドを実行できます。次に例を示します。
$ sudo ./producer &
$ sudo ./consumer
を実行すると ./consumer
、producer
バックグラウンドで実行されます。
5. 走行結果
bochs を直接終了し、 マウント後に表示できるように Ubuntu にコピーpro.txt
して コピーします。con.txt
sudo ./mount-hdc
sudo cp ./hdc/usr/root/pro.txt ./exp_07
sudo cp ./hdc/usr/root/con.txt ./exp_07
sudo chmod 777 exp_07/pro.txt
sudo chmod 777 exp_07/con.txt
pro.txt:
con.txt: