Files
codezero/tasks/mm0/src/shm.c
2009-06-02 13:19:17 +03:00

382 lines
9.8 KiB
C

/*
* Copyright (C) 2007, 2008 Bahadir Balban
*
* Posix shared memory implementation
*/
#include <shm.h>
#include <stdio.h>
#include <task.h>
#include <mmap.h>
#include <vm_area.h>
#include <globals.h>
#include <lib/malloc.h>
#include <l4lib/arch/syscalls.h>
#include <l4lib/arch/syslib.h>
#include <lib/idpool.h>
#include <lib/addr.h>
#include <lib/spinlock.h>
#include <l4/api/errno.h>
#include <l4/lib/list.h>
#include <l4/macros.h>
#include <l4/config.h>
#include <l4/types.h>
#include INC_GLUE(memlayout.h)
#include <posix/sys/ipc.h>
#include <posix/sys/shm.h>
#include <posix/sys/types.h>
#define shm_file_to_desc(shm_file) \
((struct shm_descriptor *)shm_file->priv_data)
/* Unique shared memory ids */
static struct id_pool *shm_ids;
/* Globally disjoint shm virtual address pool */
static struct address_pool shm_vaddr_pool;
void *shm_new_address(int npages)
{
return address_new(&shm_vaddr_pool, npages);
}
int shm_delete_address(void *shm_addr, int npages)
{
return address_del(&shm_vaddr_pool, shm_addr, npages);
}
int shm_pool_init()
{
int err;
/* Initialise shm id pool */
if(IS_ERR(shm_ids = id_pool_new_init(SHM_AREA_MAX))) {
printf("SHM id pool initialisation failed.\n");
return (int)shm_ids;
}
/* Initialise the global shm virtual address pool */
if ((err = address_pool_init(&shm_vaddr_pool,
SHM_AREA_START, SHM_AREA_END)) < 0) {
printf("SHM Address pool initialisation failed.\n");
return err;
}
return 0;
}
/*
* Attaches to given shm segment mapped at shm_addr if the shm descriptor
* does not already have a base address assigned. If neither shm_addr nor
* the descriptor has an address, allocates one from the shm address pool.
*/
static void *do_shmat(struct vm_file *shm_file, void *shm_addr, int shmflg,
struct tcb *task)
{
struct shm_descriptor *shm = shm_file_to_desc(shm_file);
unsigned int vmflags;
void *mapped;
if (!task) {
printf("%s:%s: Cannot find caller task with tid %d\n",
__TASKNAME__, __FUNCTION__, task->tid);
BUG();
}
if ((unsigned long)shm_addr & PAGE_MASK) {
if (shmflg & SHM_RND)
shm_addr = (void *)page_align(shm_addr);
else
return PTR_ERR(-EINVAL);
}
/* Set mmap flags for segment */
vmflags = VM_READ | VMA_SHARED | VMA_ANONYMOUS;
vmflags |= (shmflg & SHM_RDONLY) ? 0 : VM_WRITE;
/*
* Currently all tasks use the same address for each unique segment.
* If address is already assigned, the supplied address must match
* the original address. We don't look for object map count because
* utcb addresses are assigned before being mapped. NOTE: We may do
* all this in a specific shm_mmap() call in do_mmap() in the future.
*/
if (shm_file_to_desc(shm_file)->shm_addr) {
if (shm_addr && (shm->shm_addr != shm_addr))
return PTR_ERR(-EINVAL);
}
/*
* mmap the area to the process as shared. Page fault
* handler would handle allocating and paging-in the
* shared pages.
*/
if (IS_ERR(mapped = do_mmap(shm_file, 0, task,
(unsigned long)shm_addr,
vmflags, shm->npages))) {
printf("do_mmap: Mapping shm area failed with %d.\n",
(int)mapped);
return PTR_ERR(mapped);
}
/* Assign new shm address if not assigned */
if (!shm->shm_addr)
shm->shm_addr = mapped;
else
BUG_ON(shm->shm_addr != mapped);
return shm->shm_addr;
}
void *sys_shmat(struct tcb *task, l4id_t shmid, void *shmaddr, int shmflg)
{
struct vm_file *shm_file, *n;
list_foreach_removable_struct(shm_file, n, &global_vm_files.list, list) {
if (shm_file->type == VM_FILE_SHM &&
shm_file_to_desc(shm_file)->shmid == shmid)
return do_shmat(shm_file, shmaddr,
shmflg, task);
}
return PTR_ERR(-EINVAL);
}
int do_shmdt(struct tcb *task, struct vm_file *shm)
{
int err;
if ((err = do_munmap(task,
(unsigned long)shm_file_to_desc(shm)->shm_addr,
shm_file_to_desc(shm)->npages)) < 0)
return err;
return 0;
}
int sys_shmdt(struct tcb *task, const void *shmaddr)
{
struct vm_file *shm_file, *n;
list_foreach_removable_struct(shm_file, n, &global_vm_files.list, list)
if (shm_file->type == VM_FILE_SHM &&
shm_file_to_desc(shm_file)->shm_addr == shmaddr)
return do_shmdt(task, shm_file);
return -EINVAL;
}
/*
* This finds out what address pool the shm area came from and
* returns the address back to that pool. There are 2 pools,
* one for utcbs and one for regular shm segments.
*/
void shm_destroy_priv_data(struct vm_file *shm_file)
{
struct shm_descriptor *shm_desc = shm_file_to_desc(shm_file);
/* Release the shared memory address */
BUG_ON(shm_delete_address(shm_desc->shm_addr,
shm_file->vm_obj.npages) < 0);
/* Release the shared memory id */
BUG_ON(id_del(shm_ids, shm_desc->shmid) < 0);
/* Now delete the private data itself */
kfree(shm_file->priv_data);
}
/* Creates an shm area and glues its details with shm pager and devzero */
struct vm_file *shm_new(key_t key, unsigned long npages)
{
struct shm_descriptor *shm_desc;
struct vm_file *shm_file;
BUG_ON(!npages);
/* Allocate file and shm structures */
if (IS_ERR(shm_file = vm_file_create()))
return PTR_ERR(shm_file);
if (!(shm_desc = kzalloc(sizeof(struct shm_descriptor)))) {
kfree(shm_file);
return PTR_ERR(-ENOMEM);
}
/* Initialise the shm descriptor */
if (IS_ERR(shm_desc->shmid = id_new(shm_ids))) {
kfree(shm_file);
kfree(shm_desc);
return PTR_ERR(shm_desc->shmid);
}
shm_desc->key = (int)key;
shm_desc->npages = npages;
/* Initialise the file */
shm_file->length = __pfn_to_addr(npages);
shm_file->type = VM_FILE_SHM;
shm_file->priv_data = shm_desc;
shm_file->destroy_priv_data = shm_destroy_priv_data;
/* Initialise the vm object */
shm_file->vm_obj.pager = &swap_pager;
shm_file->vm_obj.flags = VM_OBJ_FILE | VM_WRITE;
/* Add to shm file and global object list */
global_add_vm_file(shm_file);
return shm_file;
}
/*
* Fast internal path to do shmget/shmat() together for mm0's
* convenience. Works for existing areas.
*/
void *shmat_shmget_internal(struct tcb *task, key_t key, void *shmaddr)
{
struct vm_file *shm_file;
struct shm_descriptor *shm_desc;
list_foreach_struct(shm_file, &global_vm_files.list, list) {
if(shm_file->type == VM_FILE_SHM) {
shm_desc = shm_file_to_desc(shm_file);
/* Found the key, shmat that area */
if (shm_desc->key == key)
return do_shmat(shm_file, shmaddr,
0, task);
}
}
return PTR_ERR(-EEXIST);
}
/*
* FIXME: Make sure hostile tasks don't subvert other tasks' shared pages
* by early-registring their shared page address here.
*/
int sys_shmget(key_t key, int size, int shmflg)
{
unsigned long npages = __pfn(page_align_up(size));
struct shm_descriptor *shm_desc;
struct vm_file *shm;
/* First check argument validity */
if (npages > SHM_SHMMAX || npages < SHM_SHMMIN)
return -EINVAL;
/*
* IPC_PRIVATE means create a no-key shm area, i.e. private to this
* process so that it would only share it with its forked children.
*/
if (key == IPC_PRIVATE) {
key = -1; /* Our meaning of no key */
if (!(shm = shm_new(key, npages)))
return -ENOSPC;
else
return shm_file_to_desc(shm)->shmid;
}
list_foreach_struct(shm, &global_vm_files.list, list) {
if (shm->type != VM_FILE_SHM)
continue;
shm_desc = shm_file_to_desc(shm);
if (shm_desc->key == key) {
/*
* Exclusive means a create request
* on an existing key should fail.
*/
if ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL))
return -EEXIST;
else
/* Found it but do we have a size problem? */
if (shm_desc->npages < npages)
return -EINVAL;
else /* Return shmid of the existing key */
return shm_desc->shmid;
}
}
/* Key doesn't exist and create is set, so we create */
if (shmflg & IPC_CREAT)
if (!(shm = shm_new(key, npages)))
return -ENOSPC;
else
return shm_file_to_desc(shm)->shmid;
else /* Key doesn't exist, yet create isn't set, its an -ENOENT */
return -ENOENT;
}
/*
* Currently, a default shm page is allocated to every thread in the system
* for efficient ipc communication. This part below provides the allocation
* and mapping of this page using shmat/get/dt call semantics.
*/
/*
* Sends shpage address information to requester. The requester then uses
* this address as a shm key and maps it via shmget/shmat.
*/
void *task_send_shpage_address(struct tcb *sender, l4id_t taskid)
{
struct tcb *task = find_task(taskid);
/* Is the task asking for its own utcb address */
if (sender->tid == taskid) {
/* It hasn't got one allocated. */
BUG_ON(!task->shared_page);
/* Return it to requester */
return task->shared_page;
/* A task is asking for someone else's utcb */
} else {
/* Only vfs is allowed to do so yet, because its a server */
if (sender->tid == VFS_TID) {
/*
* Return shpage address to requester. Note if there's
* none allocated so far, requester gets 0. We don't
* allocate one here.
*/
return task->shared_page;
}
}
return 0;
}
int shpage_map_to_task(struct tcb *owner, struct tcb *mapper, unsigned int flags)
{
struct vm_file *default_shm;
/* Allocate a new shared page address */
if (flags & SHPAGE_NEW_ADDRESS)
owner->shared_page =
shm_new_address(DEFAULT_SHPAGE_SIZE/PAGE_SIZE);
else if (!owner->shared_page)
BUG();
/* Create a new shared memory segment */
if (flags & SHPAGE_NEW_SHM)
if (IS_ERR(default_shm = shm_new((key_t)owner->shared_page,
__pfn(DEFAULT_SHPAGE_SIZE))))
return (int)default_shm;
/* Map the shared page to mapper */
if (IS_ERR(shmat_shmget_internal(mapper, (key_t)owner->shared_page,
owner->shared_page)))
BUG();
/* Prefault the owner's shared page to mapper's address space */
if (flags & SHPAGE_PREFAULT)
for (int i = 0; i < __pfn(DEFAULT_SHPAGE_SIZE); i++)
prefault_page(mapper, (unsigned long)owner->shared_page +
__pfn_to_addr(i), VM_READ | VM_WRITE);
return 0;
}
int shpage_unmap_from_task(struct tcb *owner, struct tcb *mapper)
{
return sys_shmdt(mapper, owner->shared_page);
}