實驗目的
- 深入理解操作系統的段、頁式內存管理,深入理解段表、頁表、邏輯地址、線性地址、物理地址等概念;
- 實踐段、頁式內存管理的地址映射過程;
- 編程實現段、頁式內存管理上的內存共享,從而深入理解操作系統的內存管理。
實驗內容
本次實驗的基本內容是先用Bochs調試工具跟蹤Linux 0.11的地址翻譯(地址映射)過程,然後在此基礎上編程實現一個基於內存共享的進程間通信機制。本實驗主要完成如下三項基本工作:
-
在保護模式下(啓動了分段和分頁機制以後)工作的Linux 0.11代碼中加上一個內存尋址指令,並且在該內存地址處放置一個自己構造的數據,應用Bochs調試工具跟蹤該地址的從邏輯地址、GDT表、線性地址、頁表、物理地址的過程,最後驗證是否是自己放置的數據?實際上就是手動進行一次地址翻譯工作。
-
實現兩個進程通過頁共享來進行相互通信
實驗步驟
實驗步驟參考博客:
https://www.cnblogs.com/tradoff/p/5774355.html
https://www.shiyanlou.com/courses/reports/1320023/
(侵刪)
1. 添加test.c文件
掛載hdc,在usr/root下添加test.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. 調試Linux-0.11
輸入./dbg-asm
用匯編級調試啓動linux-0.11
然後輸入以下命令編譯並運行test.c
gcc -o test test.c
./test
這時候會進入死循環,我們切換到ubuntu終端,按下Ctrl+c暫停bochs。
3. 找到物理地址
爲了讓linux-0.11中運行的test跳出循環,需要找到邏輯地址ds:0x3004對應的物理地址,將其內容(變量i)改爲0。
在終端中輸入sreg
,得到gdtr的基址值爲0x00005cb8,ldtr爲0x0068即0000 0000 0110 1000 b,可知索引爲1101b即13,TI位爲0,即GDT中的第13項爲LDT的段描述符。
輸入xp /2w 0x00005cb8+13*8
得到LDT段描述符,可以得到LDT的基址爲0x00f9a2d0
ds段選擇子爲0x0017 => 0000 0000 0001 0111 b,可知索引爲10b即2,TI位爲1,即LDT中的第2項爲ds的段描述符,輸入xp/2w 0x00f9a2d0+2*8
得到ds段描述符,可以知道ds的基址爲0x10000000,所以0x3004對應的線性地址爲0x10000000+0x3004=0x10003004
輸入xp /w 64*4
獲取頁目錄項,可知頁表基地址爲0x00fa6000。
輸入xp /w 0x00fa6000+3*4
得到物理基址爲0xfa5000。
輸入xp /w 0xfa5000+4
得到的內容即test.c中的變量的值,輸入setpmem 0xfa5004 4 0
將它設爲0。
在終端中輸入c讓bochs繼續運行,發現test跳出循環。
4. 添加系統調用
在unistd.h中添加如下代碼:
#define SHM_SIZE 64
typedef struct shm_ds
{
unsigned int key;
unsigned int size;
unsigned long page;
}shm_ds;
int sys_shmget(unsigned int key,size_t size);
void * sys_shmat(int shmid);
已及定義兩個系統調用:
#define __NR_shmget 76
#define __NR_shmat 77
修改完後把此文件覆蓋usr/include裏的unistd.h。
在sys.h中添加函數聲明
extern int sys_shmget();
extern int sys_shmat();
在system_call.s中把nr_system_calls改爲78(系統調用個數)
接下來在kernel下添加shm.c,代碼如下:
#define __LIBRARY__
#include <unistd.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <errno.h>
static shm_ds shm_list[SHM_SIZE] = {{0,0,0}};
int sys_shmget(unsigned int key, size_t size)
{
int i;
void *page;
if(size > PAGE_SIZE)
return -EINVAL;
page = get_free_page();
if(!page)
return -ENOMEM;
printk("shmget get memory's address is 0x%08x\n",page);
for(i=0; i<SHM_SIZE; i++)
{
if(shm_list[i].key == key)
return i;
}
for(i=0; i<SHM_SIZE; i++)
{
if(shm_list[i].key == 0)
{
shm_list[i].page = page;
shm_list[i].key = key;
shm_list[i].size = size;
return i;
}
}
return -1;
}
void * sys_shmat(int shmid)
{
int i;
unsigned long data_base, brk;
if(shmid < 0 || SHM_SIZE <= shmid || shm_list[shmid].page==0 || shm_list[shmid].key <= 0)
return (void *)-EINVAL;
data_base = get_base(current->ldt[2]);
printk("current's data_base = 0x%08x,new page = 0x%08x\n",data_base,shm_list[shmid].page);
brk = current->brk + data_base;
current->brk += PAGE_SIZE;
if(put_page(shm_list[shmid].page, brk) == 0)
return (void *)-ENOMEM;
return (void *)(current->brk - PAGE_SIZE);
}
然後修改Kernel下的Makefile文件
### Dependencies:
sem.s sem.o: sem.c ../include/linux/sem.h ../include/linux/kernel.h \
../include/unistd.h
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. 編寫消費者和生產者程序
在usr/root下添加producer.c和consumer.c
Producer.c代碼如下:
裏面的相關函數參考之前寫的shm.c,因爲我在第四個實驗中把sem.h分出來了,所有需要在這裏面#include <linux/sem.h>
,consumer.c也是一樣。
/*producer*/
#define __LIBRARY__
#include <stdio.h>
#include <unistd.h>
#include <linux/kernel.h>
#include <fcntl.h>
#include <sys/types.h>
#include <linux/sem.h>
_syscall2(sem_t *,sem_open,const char *,name,int,value);
_syscall1(int,sem_post,sem_t *,sem);
_syscall1(int,sem_wait,sem_t *,sem);
_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
int main(int argc, char* argv[])
{
sem_t *Empty,*Full,*Mutex;
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!");
for(i=0; i<PRODUCE_NUM; i++)
{
sem_wait(Empty);
sem_wait(Mutex);
p[location] = i;
sem_post(Mutex);
sem_post(Full);
location = (location+1) % BUFFER_SIZE;
}
return 0;
}
Consumer.c代碼如下:
/*consumer*/
#define __LIBRARY__
#include <stdio.h>
#include <unistd.h>
#include <linux/kernel.h>
#include <fcntl.h>
#include <sys/types.h>
#include <linux/sem.h>
_syscall2(sem_t *,sem_open,const char *,name,int,value);
_syscall1(int,sem_post,sem_t *,sem);
_syscall1(int,sem_wait,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
int main(int argc, char* argv[])
{
sem_t *Empty,*Full,*Mutex;
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");
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;
}
sem_unlink("Mutex");
sem_unlink("Full");
sem_unlink("Empty");
return 0;
}
運行bochs,輸入:
gcc -o pro producer.c
gcc -o con consumer.c
編譯這兩個程序,
然後輸入
pro > proOutput &
con > conOutput &
來同時運行這兩個程序,並將結果保存到proOutput和conOutput中。
最後輸入sync
。
關閉linux-0.11回到ubunt終端,輸入sudo less hdc/usr/root/conOutput查看結果如下: