一、 目标
Linux在2.2版本后实现了共享内存的系统调用(shm.h),但是在Linux-0.11是没有共享内存的系统调用的。本次实践的目标如下:
1、实现2个系统调用
int create_shm(int key,unsigned long size,int flag);
void* get_shm(int shmid,int flag);
create_shm的功能如下:
- 如果Key已经存在
直接返回共享内存的系统标识shmid - 如果Key不存在
打开一页物理内存作为共享内存,返回该页共享内存的shmid - flag参数可以忽略
get_shm的功能如下:
- 将shmid指定的共享页面映射到当前进程的虚拟地址空间中,并返回一个逻辑地址p,调用进程可以读写逻辑地址p来读写这一页共享内存。
- 两个进程都可以通过相同的shmid关联到同一页内存上
- 如果shmid非法,返回-1,置error为EINVAL
- flag可以忽略
注:添加系统调用的步骤
https://blog.csdn.net/Ecust_applied_math/article/details/102730987
二、伪代码分析
#define MAX_SHM 100
struct shm{
int key;
int shmid;
void * page;
};
struct shm shm_table[MAX_SHM] = {0};
int next_shm_index = 0;
int sys_create_shm(int key,unsigned long size,int flag){
#工作在内核态中,即这些代码是加载在内核段中的
if(key == 0 || nex_shm_index == MAX_SHM)
return -1;
#key不能为0,或者没有地方再存储了
for(i = 0;i < MAX_SHM;i++){
if(shm_table[i].key == key)
return shm_table[i].shmid;
}
#这里可以优化为二叉、或者红黑,库太少,懒得改了
new_page_addr = get_free_page()
#获取一个页,这个页对应一个物理页框
shm_table[next_shm_index].page = new_page_addr;
shm_table[next_shm_index].key = key;
new_shmid = shm_table[next_shm_index].shmid = key * 3;
next_shm_index++;#下个位置
return new_shmid;
#这里的shmid应该置为一个随机数的,这里为了方便,直接做了个变换
}
void * get_shm(int shmid){
for(i = 0;i < MAX_SHM;i++){
if(shm_table[i].shmid == shmid){
shm_index = i;
break;
}
}
#这里必须用顺序查找了
if(i == MAX_SHM)
return NULL;
#没找到,返回一个空指针
struct shm * pShm = &shm_table[i];
#设置一个指针指向共享内存描述符
#下面会用到put_page(linear_addr,addr)宏,
#put_page是Linux-0.11的一个宏,把虚拟内存中linear_addr所在页的页框设置为addr所在页的页框
user_linear_addr = get_base(current->ldt[1]) + current->brk;
#get_base(current->ldt[1]) + current->brk;指向的是当前进程的段中的brk,Linux-0.11未使用这一块地址,随意用
put_page(user_linear_addr,pShm->page);
return user_linear_addr;
#luser_linear_addr是用户段中的一个地址,pShm->page再内核段中
#注:在Linux-0.11中,所有的进程共享一个虚拟内存,每个按次序占用64MB,所以这个程序蛮简单的。
}
三、 步骤
3.1 编写sharemem.c
在Kernel下创建sharemem.c
//linux-0.11/kernel/sharemem.c
#include <errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#define MAX_SHM 100
struct shm{
int key;
int shmid;
void * page;
};
struct shm shm_table[MAX_SHM] = {{0},};
int next_shm_index = 0;
int sys_create_shm(int key,unsigned long size){
int i = 0,new_shmid = 0;
if(key == 0 || next_shm_index == MAX_SHM || size > 4096)
errno = ENOMEM;
for(i = 0;i < MAX_SHM;i++){
if(shm_table[i].key == key)
return shm_table[i].shmid;
}
void * new_page_addr = get_free_page();
shm_table[next_shm_index].page = new_page_addr;
shm_table[next_shm_index].key = key;
new_shmid = key * 3 + 47;
shm_table[next_shm_index].shmid = new_shmid;
next_shm_index++;
return new_shmid;
}
void * sys_get_shm(int key){
int i = 0;
for(i = 0;i < MAX_SHM;i++){
if(shm_table[i].shmid == shmid)
break;
}
if(i == MAX_SHM)
return NULL;
struct shm * pShm = &shm_table[i];
extern struct task_struct * current;
unsigned long user_linear_addr = (unsigned long)get_base(current->ldt[1]) + current->brk;
put_page(pShm->page,user_linear_addr);
return current->brk;
}
3.2 注册系统调用
- 在include/linux/sys.h中注册所添加的系统调用。添加两行,新增两项。
extern int sys_create_shm();
extern int sys_get_shm();
fn_ptr sys_call_table[] = { .....,sys_create_shm,sys_get_shm };
- 修改kernel/system_call.s中的系统调用数量
nr_system_calls = 74
3.3 编译
make clean
make
四、 测试结果
在Bochs中编写程序
//p.c
#define __LIBRARY__
#define __NR_create_shm 72
#define __NR_get_shm 73
#include <unistd.h>
#include <stdio.h>
_syscall2(int,create_shm,int,key,unsigned long,size)
_syscall1(void*,get_shm,int,shmid)
int main(void){
int t = create_shm(3,1024);
int * p = (int*)get_shm(t);
? printf("shmid = %d\n",t);
printf("Logical address is %p\n",p);
p[0] = 4;
while(p[0]);
return 0;
}
//c.c
#define __LIBRARY__
#define __NR_create_shm 72
#define __NR_get_shm 73
#include <unistd.h>
#include <stdio.h>
_syscall2(int,create_shm,int,key,unsigned long,size)
_syscall1(void*,get_shm,int,shmid)
int main(void){
int t = create_shm(3,1024);
int * p = (int*)get_shm(t);
printf("Shared Memory = %d\n",p[0]);
return 0;
}
可以看出,进程C顺利的读取到了进程A的地址空间的值。
五、 总结
- 共享内存的核心是使得进程A的内存空间中的一页和进程B的内存空间中的一页映射到同一个物理页框上。
- get_free_page()返回的是一个逻辑地址,不过是在内核段中。
- 项目地址
https://github.com/ecustlmc/linux-0.11-lab/tree/share_mem