天天看點

哈工大李治軍老師作業系統實驗之位址映射與共享

文章目錄

    • 實驗目的
    • 實驗内容
    • 實驗過程
      • 1. 跟蹤Linux 0.11 的位址翻譯過程
      • 2. 在Ubuntu上實作多程序的生産者-消費者出現,用共享緩沖區
      • 3. 在信号量的基礎上為Linux0.11增加共享記憶體功能,并将生産者-消費者模型移植到Linux0.11進行測試
        • 3.1 前置知識
        • 3.2 實作過程

實驗目的

  • 深入了解作業系統的段、頁式記憶體管理,深入了解段表、頁表、邏輯位址、線性位址、實體位址等概念
  • 實踐段、頁式記憶體管理的位址映射過程
  • 程式設計實作段、頁式記憶體管理的記憶體共享,進而深入了解作業系統的記憶體管理

實驗内容

  • 用Bochs調試工具跟蹤Linux 0.11 的位址翻譯(位址映射)過程,了解IA-21和Linux 0.11 的記憶體管理機制
  • 在Ubuntu上編寫多程序的生産者-消費者程式,用共享記憶體作為緩沖區
  • 在信号量實驗的基礎上,為Linux0.11 增加共享記憶體功能,并将生産者-消費者程式移植到Linux 0.11當中。

實驗過程

1. 跟蹤Linux 0.11 的位址翻譯過程

略,按照教程走就不會出現問題

2. 在Ubuntu上實作多程序的生産者-消費者出現,用共享緩沖區

這部分直接給出代碼,主要是知道怎麼調用函數。

//生産者producer.c
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>

#define MAX_NUMBER 500
#define MAX_BUFFER 10

_syscall2(int, 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);
_syscall3(int, shmget, int, key, size_t, size, int, shmflg);
_syscall3(int, shmat, int, shmid, const void *, shmaddr, int, shmflg);

int main(int argc, char * argv[]){
    sem_t* full = (sem_t*)sem_open("full", 0);
    sem_t* mutex = (sem_t*)sem_open("mutex", 1);
    sem_t* empty = (sem_t*)sem_open("empty", MAX_BUFFER);

    int position, shmid;
    int *data;
    int key = 9999;
    int i;

    shmid = shmget(key, (MAX_BUFFER+1)*sizeof(int), 0);
    data = (int*)shmat(shmid, 0, 0);
    /*
    printf("the shmid is %d, the data is %x\n", shmid, data);
    */

    for(i=0; i < MAX_NUMBER+1; i++){
        sem_wait(empty);
        sem_wait(mutex);

        position = (i % MAX_BUFFER);
        data[position] = i;

        sem_post(mutex);
        sem_post(full);
    }
    return 0;
}
           
//消費者consumer.c
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>

#define MAX_NUMBER 500
#define MAX_BUFFER 10

_syscall2(int, 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);
_syscall3(int, shmget, int, key, size_t, size, int, shmflg);
_syscall3(int, shmat, int, shmid, const void *, shmaddr, int, shmflg);

int main(int argc, char * argv[]){
    sem_t* full = (sem_t*)sem_open("full", 0);
    sem_t* mutex = (sem_t*)sem_open("mutex", 1);
    sem_t* empty = (sem_t*)sem_open("empty", MAX_BUFFER);

    int tmp, shmid;
    int position = 0;
    int *data;
    int key = 9999;

    shmid = shmget(key, (MAX_BUFFER+1)*sizeof(int), 0);
    data = (int*)shmat(shmid, 0, 0);
    /*
    printf("the shmid is %d, the data is %x\n", shmid, data);
    */
    while(1){
        sem_wait(full);
        sem_wait(mutex);

        position = (position % MAX_BUFFER);
        printf("%d: %d\n", getpid(), data[position]);
        fflush(stdout);
        if(data[position] == MAX_NUMBER){
            sem_post(mutex);
            sem_post(empty);
            break;
        }
        position++;
        
        sem_post(mutex);
        sem_post(empty);
    }
    sem_unlink("full");
    sem_unlink("mutex");
    sem_unlink("empty");
    return 0;
}
           

3. 在信号量的基礎上為Linux0.11增加共享記憶體功能,并将生産者-消費者模型移植到Linux0.11進行測試

實作共享緩沖區也是通過添加系統調用實作,實驗報告中已經告訴我們添加倆系統調用,是以實驗步驟依然是添加系統調用的步驟。

3.1 前置知識

首先要明白:邏輯位址、線性位址、虛拟位址、實體位址之間的差別以及如何計算,這個可以看這篇部落格。

其次明白要實作共享記憶體,需要進行的幾個步驟:

  • 在記憶體當中找一塊空閑空間傳回實體位址 。(如下圖中的實體共享記憶體)
  • 尋找空閑的虛拟位址空間建立線性位址(段基址+段内偏移)與實體位址之間的映射關系即将填寫相關頁表使得

    MMU

    能通過這個頁表找到最後的實體位址。(即填寫圖中的頁表)
  • 更新邏輯位址。(即填寫更新的段表)
哈工大李治軍老師作業系統實驗之位址映射與共享
這張圖從左往右看是位址翻譯的過程,也是位址跟蹤的過程;從右往左看是我們需要實作共享記憶體的過程,也就是說我們要做的就是從實體位址出發,一步步使得各個程序都能看到同一塊實體記憶體的過程!

3.2 實作過程

由于之前梳理過如何添加系統調用,是以本文直接讨論如何實作共享記憶體,不再叙述添加系統調用的過程。

共享記憶體的實作嚴格按照上面步驟進行。

  1. 在記憶體當中尋找一塊空閑空間并傳回實體位址

    這個實驗報告當中也給出了方法:通過調用

    get_free_page

    函數,該函數會幫我們找到記憶體當中空閑區域并傳回該區域起始實體位址。這一步我們在

    shmget

    函數中實作
    int allkey[20];
    void *allAddress[20];
    int countKey;
    /**
     * @brief:
     * @note   
     * @param  key: 表示該頁在記憶體空間中的辨別,由使用者自定義
     * @param  size: 申請空間大小
     * @param  shmflg: 設定屬性,由于實驗報告沒有要求,可以忽略
     * @retval 
     */
    int sys_shmget(int key,size_t size,int shmflg)
    {
       int i;
       /*
       首先根據辨別周遊已經建立的共享記憶體,如果已經建立則傳回共享記憶體位址(意味着與其他程序共享)
       */
       for(i=0;i<countKey;i++) if(allkey[i]==key)return key;
       //4096=4KB,表示一頁大小。隻能申請一頁大小的共享記憶體
       if(size>4096)
        {
          errno=EINVAL;
          return -1;
        }
       /*
       如果之前沒有建立過key辨別的共享記憶體則現在建立
       */
       int tmp=get_free_page();
       if(tmp==0)
       {
           printk("Have no page!!!\n");
           errno=ENOMEM;
           return -1;
       }
       /*
       将建立好的共享記憶體納入控制數組友善與其他程序共享,也是為位址映射做好準備
       */
       allkey[countKey]=key;
       allAddress[countKey]=(void*)tmp;
       countKey++;
       return key;
    }
               
  2. 尋找空閑的虛拟位址空間建立線性位址(段基址+段内偏移)與實體位址之間的映射關系

    這一步也就是填寫頁表的過程,因為線性位址通過頁表找到最終的實體位址,是以也是比較容易完成的,實驗報告也給出了方法。在雖然給出了方法,但是我們還是需要拿出一張虛拟記憶體表來分析:

    哈工大李治軍老師作業系統實驗之位址映射與共享
    從虛拟記憶體空間示意圖我們知道堆與棧之間的虛拟記憶體是沒有被使用的,是以我們可以在這個區間申請一塊記憶體用于建立頁表與實際的實體記憶體進行映射。同時我們可以發現

    brk

    指針正好指向堆區頂部,我們可以利用這個指針幫我們定位需要申請的空間首位址。
    報告中選擇資料段中的一段記憶體,而我選擇

    brk

    指針後的空閑區域,幸運的是在程序

    PCB

    當中記錄了brk指針的 邏輯位址,然後加上程序開始處的虛拟位址就可以得到brk指針的虛拟位址或者叫線性位址(虛拟位址=段基址+邏輯位址)。
    /**
     * @brief :位址映射
     * @note   
     * @param  shmid: shmget函數傳回的實體位址辨別,用于獲得真正的實體位址
     * @param  *shmaddr: 這不是實體位址!!!,直接寫0,可忽略
     * @param  shmflg: 可忽略
     * @retval 
     */
    int sys_shmat(int shmid,const void *shmaddr,int shmflg)
    {
        unsigned long  tmp;
        int i;
        void* result;
        //根據辨別查找要映射的實體位址
        for(i=0;i<countKey;i++)if(allkey[i]==shmid)break;
        if(i==countKey)return -1;
        //result存儲着真正的實體位址
        result=allAddress[i];
        //擷取brk指針的邏輯位址
        tmp=current->brk;
        //current->brk+=PAGE_SIZE;
        //current->start_code:程序開始處的虛拟位址,建立線性位址與實體位址的映射關系
        if(!put_page(result,current->start_code+tmp))
        {
           errno=EINVAL;
           return -1;
        }
        return tmp;
    }
               
  3. 更新程序虛拟記憶體的邏輯位址

    這一步就是更新以下段内的偏移位址(邏輯位址),因為添加了一個共享記憶體空間,段内其他部分的 偏移位址需要更新。在步驟2的基礎上加一行位移代碼就行(更新brk指針的邏輯位址)。

    /**
     * @brief :位址映射
     * @note   
     * @param  shmid: shmget函數傳回的實體位址辨別,用于獲得真正的實體位址
     * @param  *shmaddr: 這不是實體位址!!!,直接寫0,可忽略
     * @param  shmflg: 可忽略
     * @retval 
     */
    int sys_shmat(int shmid,const void *shmaddr,int shmflg)
    {
        unsigned long  tmp;
        int i;
        void* result;
        //根據辨別查找要映射的實體位址
        for(i=0;i<countKey;i++)if(allkey[i]==shmid)break;
        if(i==countKey)return -1;
        //result存儲着真正的實體位址
        result=allAddress[i];
        //擷取brk指針的邏輯位址
        tmp=current->brk;
        //更新brk邏輯位址,由于就申請了一頁空間,就把brk指針位移一個頁的大小就行。
        //PAGE_SIZE=0x4000000
        current->brk+=PAGE_SIZE;
        //current->start_code:程序開始處的虛拟位址,建立線性位址與實體位址的映射關系
        if(!put_page(result,current->start_code+tmp))
        {
           errno=EINVAL;
           return -1;
        }
        //傳回共享記憶體的首位址
        return tmp;
    }
               
    實作共享記憶體的系統調用已經寫好了,這裡給出完整的

    shm.c

    代碼
    #include<errno.h>
    #include<unistd.h>
    #include<sys/wait.h>
    #include<a.out.h>
    #include<sys/types.h>
    #include<linux/sched.h>
    #include<linux/kernel.h>
    int allkey[20];
    void *allAddress[20];
    int countKey;
    /**
     * @brief:
     * @note   
     * @param  key: 表示該頁在記憶體空間中的辨別,由使用者自定義
     * @param  size: 申請空間大小
     * @param  shmflg: 設定屬性,由于實驗報告沒有要求,可以忽略
     * @retval 
     */
    int sys_shmget(int key,size_t size,int shmflg)
    {
       int i;
       /*
       首先根據辨別周遊已經建立的共享記憶體,如果已經建立則傳回共享記憶體位址(意味着與其他程序共享)
       */
       for(i=0;i<countKey;i++) if(allkey[i]==key)return key;
       //4096=4KB,表示一頁大小。隻能申請一頁大小的共享記憶體
       if(size>4096)
        {
          errno=EINVAL;
          return -1;
        }
       /*
       如果之前沒有建立過key辨別的共享記憶體則現在建立
       */
       int tmp=get_free_page();
       if(tmp==0)
       {
           printk("Have no page!!!\n");
           errno=ENOMEM;
           return -1;
       }
       /*
       将建立好的共享記憶體納入控制數組友善與其他程序共享,也是為位址映射做好準備
       */
       allkey[countKey]=key;
       allAddress[countKey]=(void*)tmp;
       countKey++;
       return key;
    }
    /**
     * @brief :位址映射
     * @note   
     * @param  shmid: shmget函數傳回的實體位址辨別,用于獲得真正的實體位址
     * @param  *shmaddr: 這不是實體位址!!!,直接寫0,可忽略
     * @param  shmflg: 可忽略
     * @retval 
     */
    int sys_shmat(int shmid,const void *shmaddr,int shmflg)
    {
        unsigned long  tmp;
        int i;
        void* result;
        //根據辨別查找要映射的實體位址
        for(i=0;i<countKey;i++)if(allkey[i]==shmid)break;
        if(i==countKey)return -1;
        //result存儲着真正的實體位址
        result=allAddress[i];
        //擷取brk指針的邏輯位址
        tmp=current->brk;
        //更新brk邏輯位址,由于就申請了一頁空間,就把brk指針位移一個頁的大小就行。
        //PAGE_SIZE=0x4000000
        current->brk+=PAGE_SIZE;
        //current->start_code:程序開始處的虛拟位址,建立線性位址與實體位址的映射關系
        if(!put_page(result,current->start_code+tmp))
        {
           errno=EINVAL;
           return -1;
        }
        //傳回共享記憶體的首位址
        return tmp;
    }
    
               
  4. 修改

    Linux/mm/

    目錄下的

    Makefile

    ...
    OBJS	= memory.o page.o shm.o
    ...
    ...
    shm.o: shm.c ../include/errno.h ../include/unistd.h ../include/sys/wait.h \
      ../include/sys/types.h ../include/linux/sched.h ../include/a.out.h
      ../include/linux/kernel.h
               
    以防萬一,這裡也給出

    Linux/kernel

    目錄下的

    Makefile

    檔案
    #
    # Makefile for the FREAX-kernel.
    #
    # Note! Dependencies are done automagically by 'make dep', which also
    # removes any old dependencies. DON'T put your own dependencies here
    # unless it's something special (ie not a .c file).
    #
    
    AR	=ar
    AS	=as --32
    LD	=ld
    LDFLAGS	=-m elf_i386 -x
    CC	=gcc-3.4 -march=i386
    CFLAGS	=-m32 -g -Wall -O -fstrength-reduce -fomit-frame-pointer \
    	-finline-functions -nostdinc -I../include
    CPP	=gcc-3.4 -E -nostdinc -I../include
    
    .c.s:
    	$(CC) $(CFLAGS) \
    	-S -o $*.s $<
    .s.o:
    	$(AS) -o $*.o $<
    .c.o:
    	$(CC) $(CFLAGS) \
    	-c -o $*.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 sem.o
    
    kernel.o: $(OBJS)
    	$(LD) -m elf_i386 -r -o kernel.o $(OBJS)
    	sync
    
    clean:
    	rm -f core *.o *.a tmp_make keyboard.s
    	for i in *.c;do rm -f `basename $$i .c`.s;done
    	(cd chr_drv; make clean)
    	(cd blk_drv; make clean)
    	(cd math; make clean)
    
    dep:
    	sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    	(for i in *.c;do echo -n `echo $$i | sed 's,\.c,\.s,'`" "; \
    		$(CPP) -M $$i;done) >> tmp_make
    	cp tmp_make Makefile
    	(cd chr_drv; make dep)
    	(cd blk_drv; make dep)
    
    ### Dependencies:
    sem.s sem.o: sem.c ../include/linux/kernel.h ../include/unistd.h
    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
    fork.s fork.o: fork.c ../include/errno.h ../include/linux/sched.h \
      ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
      ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h \
      ../include/asm/segment.h ../include/asm/system.h
    mktime.s mktime.o: mktime.c ../include/time.h
    panic.s panic.o: panic.c ../include/linux/kernel.h ../include/linux/sched.h \
      ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
      ../include/linux/mm.h ../include/signal.h
    printk.s printk.o: printk.c ../include/stdarg.h ../include/stddef.h \
      ../include/linux/kernel.h
    sched.s sched.o: sched.c ../include/linux/sched.h ../include/linux/head.h \
      ../include/linux/fs.h ../include/sys/types.h ../include/linux/mm.h \
      ../include/signal.h ../include/linux/kernel.h ../include/linux/sys.h \
      ../include/linux/fdreg.h ../include/asm/system.h ../include/asm/io.h \
      ../include/asm/segment.h
    signal.s signal.o: signal.c ../include/linux/sched.h ../include/linux/head.h \
      ../include/linux/fs.h ../include/sys/types.h ../include/linux/mm.h \
      ../include/signal.h ../include/linux/kernel.h ../include/asm/segment.h
    sys.s sys.o: sys.c ../include/errno.h ../include/linux/sched.h \
      ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
      ../include/linux/mm.h ../include/signal.h ../include/linux/tty.h \
      ../include/termios.h ../include/linux/kernel.h ../include/asm/segment.h \
      ../include/sys/times.h ../include/sys/utsname.h
    traps.s traps.o: traps.c ../include/string.h ../include/linux/head.h \
      ../include/linux/sched.h ../include/linux/fs.h ../include/sys/types.h \
      ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h \
      ../include/asm/system.h ../include/asm/segment.h ../include/asm/io.h
    vsprintf.s vsprintf.o: vsprintf.c ../include/stdarg.h ../include/string.h
               
    當然,由于本人知識水準有限,如果文中有重大錯誤,請務必告訴我,非常感謝!

繼續閱讀