操作系統實驗報告-共享內存


實驗要求

與信號量實驗中的pc.c的功能要求基本一致,僅有兩點不同:

  1. 不用文件做緩沖區,而是使用共享內存;
  2. 生產者和消費者分別是不同的程序。生產者是producer.c,消費者是consumer.c。兩個程序都是單進程的,通過信號量和緩沖區進行通信。

具體要求在mm/shm.c中實現shmget()和shmat()兩個系統調用。它們能支持producer.c和consumer.c的運行即可,不需要完整地實現POSIX所規定的功能。

shmget()

int shmget(key_t key, size_t size, int shmflg);

shmget()會新建/打開一頁內存,並返回該頁共享內存的shmid(該塊共享內存在操作系統內部的id)。所有使用同一塊共享內存的進程都要使用相同的key參數。如果key所對應的共享內存已經建立,則直接返回shmid。如果size超過一頁內存的大小,返回-1,並置errno為EINVAL。如果系統無空閑內存,返回-1,並置errno為ENOMEM。shmflg參數可忽略。

shmat()

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmat()會將shmid指定的共享頁面映射到當前進程的虛擬地址空間中,並將其首地址返回。如果shmid非法,返回-1,並置errno為EINVAL。shmaddr和shmflg參數可忽略。

實驗步驟

添加系統調用

include/unistd.h中添加:

typedef int key_t;
int shmget(key_t key, unsigned int size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);

typedef struct shm_ds{
    key_t key;
    unsigned long page;
    unsigned int size;
    unsigned int attach;
}shm_ds;

其他瑣碎的添加系統調用的步驟略去,添加mm/shm.c實現這幾個系統調用:

#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>

#define PAGE_SIZE    4096
#define PAGING_MEMORY (15*1024*1024)

#define NR_SHM    64

shm_ds shms[NR_SHM]={{0,0,0,0}};

static inline volatile void oom(void)
{
    printk("mm/shm.c: out of memory\n\r");
}

int sys_shmget(key_t key, unsigned int size, int shmflg)
{
    int i;

    if(size>PAGE_SIZE)
    {
        printk("shmget: size %u cannot be greater than the page size %ud. \n", size, PAGE_SIZE);
        return -ENOMEM;
    }

    if(key==0)
    {
        printk("shmget: key cannot be 0.\n");
        return -EINVAL;
    }

    /* 若共享內存描述符已存在,直接返回索引 */
    for(i=0; i<NR_SHM; i++)
        if(shms[i].key==key)
            return i;

    /* 找到一個未用的共享內存描述符初始化,並返回索引 */
    for(i=0; i<NR_SHM; i++)
        if(shms[i].key==0)
        {
            if(!(shms[i].page = get_free_page()))
            {
                oom();
                return -ENOMEM;
            }
            shms[i].key = key;
            shms[i].size = size;
            shms[i].attach = 0;
            return i;
        }

    /* 若所有共享描述符都已用,打印提示 */
    printk("shmget: no free shared memory description.\n");
    return -ENOMEM;
}

void *sys_shmat(int shmid, const void *shmaddr, int shmflg)
{


    unsigned long data_base, brk;
    unsigned long *dir, *page_table;
    unsigned long tmp;

    if(shmid<0 || shmid>NR_SHM)
    {
        printk("shmat: shmid id is invalid.\n");
        return -EINVAL;
    }

    data_base = get_base(current->ldt[2]);    /* 獲取數據段的線性地址 */

    printk("code base: 0x%08x, code limit: 0x%08x \ndata base: 0x%08x, data limit: 0x%08x \nbrk: 0x%08x\n",
            get_base(current->ldt[1]), get_limit(current->ldt[1]),
            data_base, get_limit(current->ldt[2]), current->brk);

    brk = current->brk + data_base;    /* 代碼段+數據段+bss的結尾地址 */

    /* 當brk與棧頂之間還有一頁內存時,嘗試映射 */
    while(brk < data_base + current->tss.esp0)
    {
        /* 下面的代碼可參考put_page(shms[shmid].page, data_base); */
        dir = (unsigned long *)((brk >> 20) & 0xffc);    /* 頁目錄表項地址 */
        if(!(1 & *dir))    /* 若頁目錄表項未啟用則啟用 */
        {
            if(!(tmp = get_free_page()))
            {
                oom();
                return -ENOMEM;
            }
            else
                *dir = tmp | 7;
        }

        /* 頁表項地址 */
        page_table = (unsigned long *)((0xfffff000 & *dir) + (0xffc & (brk >> 10)));

        brk += PAGE_SIZE;    /* 將已用內存結尾指針后移一頁 */

        if(1 & *page_table)    /* 若頁表項已啟用,則嘗試映射下一頁 */
            continue;

        *page_table = shms[shmid].page | 7;    /* 將頁表項映射到共享內存描述符申請的物理內存頁 */
        printk("linear address 0x%08x links to physical page 0x%08x. \n",
                brk - PAGE_SIZE, shms[shmid].page);
        shms[shmid].attach++;    /* 增加共享內存的關聯計數 */
        break;

    }
    current->brk = brk;    /* 更新已用內存結尾地址 */

    /* 返回共享頁的邏輯地址 */
    return (void *)(brk - PAGE_SIZE - data_base);

}

int sys_shmdt(const void *shmaddr)
{
    unsigned long *dir, *page_table;
    int i;

    shmaddr += get_base(current->ldt[2]);    /* 線性地址 */

    dir = (unsigned long *)(0xffc & ((unsigned long)shmaddr >> 20));
    if(!(1 & *dir))
    {
        printk("shmdt: page directory entry not used.\n");
        return -EINVAL;
    }

    page_table = (unsigned long *)((0xfffff000 & *dir) + (0xffc & ((unsigned long)shmaddr >> 10)));
    if(!(1 & *page_table))
    {
        printk("shmdt: page table entry not used.\n");
        return -EINVAL;
    }

    *page_table &= 0xfffff000;

    for(i=0; i<NR_SHM; i++)
        if(shms[i].page == *page_table)
        {
            if((--(shms[i].attach))<=0)    /* 若與此共享內存關聯的計數為0,則釋放此描述符與對應內存 */
            {
                free_page(shms[i].page);
                shms[i].key=0;
                break;
            }
        }
    *page_table = 0;

    return 0;
}

編寫基於內存共享的生產者-消費者測試程序

shm.h定義了一些宏、信號量名稱、產品結構體:

#define NR_BUFFER    10    /* 緩存區可存放的產品數 */
#define NR_ITEMS    50    /* 產品總數 */
typedef int    item_t;
#define ITEM_SIZE sizeof(item_t)

/* 這個結構體模擬了一個隊列 */
typedef struct{
    volatile item_t buffers[NR_BUFFER];
    volatile unsigned int tail;    /* 生產者的隊尾索引,用於產品入隊 */
    volatile unsigned int head;    /* 消費者的隊首索引,用於產品出隊 */
}shm_t;

#define SHM_SIZE    sizeof(shm_t)    /* 結構體的大小,作為申請共享內存大小的參數 */

#define NR_CONSUMERS    5

#define KEY    1070

char metux_name[6] = "METUX";
char full_name[5] = "FULL";
char empty_name[6] = "EMPTY";

producer.c:

#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "shm.h"

_syscall3(int,shmget,key_t,key,unsigned int,size,int,shmflg)
_syscall3(void *,shmat,int,shmid,const void *,shmaddr,int,shmflg)
_syscall1(int,shmdt,const void *,shmaddr)

_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)

sem_t *metux, *full, *empty;
unsigned int *tail;

int main(void)
{
    int shmid;
    shm_t *shmaddr;
    key_t key;
    int item;

    shmid = shmget(KEY, SHM_SIZE, 0);    /* 根據key獲取共享內存id */
    if(shmid == -1)
    {
        printf("shmid: %d\n", shmid);
        printf("errno: %d\n", errno);
        return -1;
    }

    shmaddr = (shm_t *)shmat(shmid, NULL, 0);    /* 映射共享內存,取得邏輯地址 */
    if(shmaddr == -1)
    {
        printf("shmaddr: %ld\n", (unsigned long)shmaddr);
        printf("errno: %d\n", errno);
        return -1;
    }

    metux = sem_open(metux_name, 1);
    full = sem_open(full_name, 0);
    empty = sem_open(empty_name, NR_BUFFER);
    item = 0;
    tail = &(shmaddr->tail);    /* 獲取生產隊列的隊尾指針,初始化隊首和隊尾 */
    *tail = 0;
    shmaddr->head = 0;

    printf("producer created...\n");
    fflush(stdout);

    while(item <= NR_ITEMS)
    {

        sem_wait(empty);
        sem_wait(metux);

        shmaddr->buffers[(*tail) % NR_BUFFER] = item;    /* 放入商品 */
        printf("produce item %d at pos %u(index %u) when head is %d. \n",
                item++, *tail, ((*tail)++)%NR_BUFFER, shmaddr->head);
        fflush(stdout);

        sem_post(metux);
        sem_post(full);
    }

    if(shmdt((void *)shmaddr))    /* 解除映射 */
        printf("shmdt errno: %d\n", errno);
    else
        printf("shmdt: OK. \n");

    return 0;
}

consumer.c:

#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "shm.h"

_syscall3(int,shmget,key_t,key,unsigned int,size,int,shmflg)
_syscall3(void *,shmat,int,shmid,const void *,shmaddr,int,shmflg)
_syscall1(int,shmdt,const void *,shmaddr)

_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)

sem_t *metux, *full, *empty;
unsigned int *tail;

int main(void)
{
    int shmid;
    shm_t *shmaddr;
    key_t key;
    int i, j, pid;
    int item;

    shmid = shmget(KEY, SHM_SIZE, 0);
    if(shmid == -1)
    {
        printf("shmid: %d\n", shmid);
        printf("errno: %d\n", errno);
        return -1;
    }

    metux = sem_open(metux_name, 1);
    full = sem_open(full_name, 0);
    empty = sem_open(empty_name, NR_BUFFER);
    item = 0;

    i = 0;
    while(pid=fork(), ++i <= NR_CONSUMERS)
        if(!pid)    /* 子進程作為消費者 */
        {
            pid = getpid();
            printf("pid %d: consumer %d created...\n", pid, i);
            fflush(stdout);

            shmaddr = (shm_t *)shmat(shmid, NULL, 0);
            if(shmaddr == -1)
            {
                printf("pid %d: shmaddr: %ld\n", pid, (unsigned long)shmaddr);
                printf("pid %d: errno: %d\n", pid, errno);
                return -1;
            }
            tail = &(shmaddr->head);

            while(1)
            {
                sem_wait(full);

                /* 這一段用於結束消費者進程。
                 * 當最后一個消費品在被消費時,其他消費者因為因full為0而處於等待full信號量的狀態,
                 * 需要post一個full信號量使得其他消費者進程能夠繼續sem_wait(full)之后的代碼,
                 * 下面的代碼解除了進程對共享內存的映射,
                 * 並post一個full使得另一個消費者進程也能進入到這段代碼 */
                if(*tail > NR_ITEMS)
                {
                    if(shmdt((void *)shmaddr))
                        printf("pid %d: shmdt errno: %d\n", pid, errno);

                    printf("pid %d: post semaphore FULL to kill other consumers!\n", pid);
                    sem_post(full);

                    return 0;
                }

                sem_wait(metux);

                item = shmaddr->buffers[(*tail) % NR_BUFFER];    /* 取出產品 */

                sem_post(metux);

                printf("pid %d: consumer %d consume item %d at pos %u(index %u) when head is %d. \n",
                        pid, i, item, *tail, ((*tail)++) % NR_BUFFER, shmaddr->tail);
                fflush(stdout);

                sem_post(empty);

                if(*tail > NR_ITEMS)
                {
                    printf("pid %d: post semaphore FULL to kill other consumers!\n", pid);
                    sem_post(full);
                }
            }
        }

    shmaddr = (shm_t *)shmat(shmid, NULL, 0);
    if(shmaddr == -1)
    {
        printf("pid %d: shmaddr: %ld\n", pid, (unsigned long)shmaddr);
        printf("pid %d: errno: %d\n", pid, errno);
        return -1;
    }
    tail = &(shmaddr->head);

    while(*tail <= NR_ITEMS);    /* 父進程等待子進程消費完產品 */

OK:
    if(shmdt((void *)shmaddr))
        printf("pid %d: shmdt errno: %d\n", pid, errno);

    sem_unlink(metux_name);
    sem_unlink(full_name);
    sem_unlink(empty_name);

    printf("pid %d: semaphore unlink OK\n", pid);

    return 0;
}

編譯運行linux-0.11進行測試

將掛載虛擬硬盤,將unistd.h覆蓋(略)。

將以上shm.h、producer.c、consumer.c復制到虛擬硬盤。

編譯運行linux-0.11,在linux-0.11中編譯producer.c、consumer.c:

gcc -o producer producer.c
gcc -o consumer consumer.c

運行:

./producer > producerOut &  ; 這里將輸出重定向到文件producerOut,
                                              ; 並把此任務掛在后台,以便運行consumer
./consumer > consumerOut

得到輸出:

producerOut:

producer created...
produce item 0 at pos 1(index 0) when head is 0.
produce item 1 at pos 2(index 1) when head is 0.
produce item 2 at pos 3(index 2) when head is 0.
produce item 3 at pos 4(index 3) when head is 0.
produce item 4 at pos 5(index 4) when head is 0.
produce item 5 at pos 6(index 5) when head is 0.
produce item 6 at pos 7(index 6) when head is 0.
produce item 7 at pos 8(index 7) when head is 0.
produce item 8 at pos 9(index 8) when head is 0.
produce item 9 at pos 10(index 9) when head is 0.
produce item 10 at pos 11(index 0) when head is 10.
produce item 11 at pos 12(index 1) when head is 10.
produce item 12 at pos 13(index 2) when head is 10.
produce item 13 at pos 14(index 3) when head is 10.
......
produce item 46 at pos 47(index 6) when head is 40.
produce item 47 at pos 48(index 7) when head is 40.
produce item 48 at pos 49(index 8) when head is 40.
produce item 49 at pos 50(index 9) when head is 40.
produce item 50 at pos 51(index 0) when head is 50.
shmdt: OK.

consumerOut:

pid 24: consumer 5 created...
pid 23: consumer 4 created...
pid 22: consumer 3 created...
pid 21: consumer 2 created...
pid 20: consumer 1 created...
pid 20: consumer 1 consume item 0 at pos 1(index 0) when head is 10.
pid 20: consumer 1 consume item 1 at pos 2(index 1) when head is 10.
pid 20: consumer 1 consume item 2 at pos 3(index 2) when head is 10.
pid 20: consumer 1 consume item 3 at pos 4(index 3) when head is 10.
pid 20: consumer 1 consume item 4 at pos 5(index 4) when head is 10.
pid 20: consumer 1 consume item 5 at pos 6(index 5) when head is 10.
pid 20: consumer 1 consume item 6 at pos 7(index 6) when head is 10.
pid 20: consumer 1 consume item 7 at pos 8(index 7) when head is 10.
pid 20: consumer 1 consume item 8 at pos 9(index 8) when head is 10.
pid 20: consumer 1 consume item 9 at pos 10(index 9) when head is 10.
pid 24: consumer 5 consume item 10 at pos 11(index 0) when head is 20.
pid 24: consumer 5 consume item 11 at pos 12(index 1) when head is 20.
pid 24: consumer 5 consume item 12 at pos 13(index 2) when head is 20.
pid 24: consumer 5 consume item 13 at pos 14(index 3) when head is 20.
pid 24: consumer 5 consume item 14 at pos 15(index 4) when head is 20.
......
pid 20: consumer 1 consume item 45 at pos 46(index 5) when head is 50.
pid 20: consumer 1 consume item 46 at pos 47(index 6) when head is 50.
pid 20: consumer 1 consume item 47 at pos 48(index 7) when head is 50.
pid 20: consumer 1 consume item 48 at pos 49(index 8) when head is 50.
pid 20: consumer 1 consume item 49 at pos 50(index 9) when head is 50.
pid 24: consumer 5 consume item 50 at pos 51(index 0) when head is 51.
pid 24: post semaphore FULL to kill other consumers!
pid 24: post semaphore FULL to kill other consumers!
pid 23: post semaphore FULL to kill other consumers!
pid 22: post semaphore FULL to kill other consumers!
pid 21: post semaphore FULL to kill other consumers!
pid 20: post semaphore FULL to kill other consumers!
pid 0: semaphore unlink OK
pid 25: semaphore unlink OK

這里出了個bug,有個pid為25的進程也執行了父進程的代碼(取消關聯信號量)。我暫時找不到問題所在(我只fork了5個子進程(20-24),我們可以看到它們都post了一個full信號量,然后return了)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM