实验要求
与信号量实验中的pc.c的功能要求基本一致,仅有两点不同:
- 不用文件做缓冲区,而是使用共享内存;
- 生产者和消费者分别是不同的程序。生产者是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#include #include #include #include #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 ) if(shms[i].key==key) return i; /* 找到一个未用的共享内存描述符初始化,并返回索引 */ for(i=0; 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 ) 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#include #include <string.h> #include #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#include #include <string.h> #include #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了)。