Tuesday, February 3, 2009

*nix: XSI shared memory

When you work with POSIX shared memory objects you can get the size of the shared memory space assigned to key by opening with shm_open routine and and checking st_size field of struct stat obtained from fstat libc call. Then you use this value to map memory area into the userspace with mmap.

With System V IPC model you are using int shmget(key_t key, size_t size, int shmflg) routine to map the shared memory. For mapping already existing object you should call it with value of size exactly to the size of existing shared memory object. According to man pagesyou'll get EINVAL in case of size is less than the system-imposed minimum or greater than the system-imposed maximum. Also you are not allowed to specify size less than actual size of existing memory segment for the key.
In some manuals I saw remark for tuple of EINVAL and size:

[EINVAL]
No shared memory segment is to be created and a shared memory segment exists for key but the size of the segment associated with it is less than size and size is not 0.

In comp.programming.threads there was a discussion regarding size argument for shmget. From the conversation I concluded that size is used _only_for_creation_ of the memory segment.
Walking through the sources of linux kernel I've found that if the key exists and other lags are ok, the kernel finally calls shm_more_checks:
static inline int shm_more_checks(struct kern_ipc_perm *ipcp,
                                struct ipc_params *params)
{
        struct shmid_kernel *shp;

        shp = container_of(ipcp, struct shmid_kernel, shm_perm);
        if (shp->shm_segsz < params->u.size)
                return -EINVAL;

        return 0;
}
shm_more_checks indeed just checks if requested size is less or equal than the size of the shared object and if this conditions is satisfied this routine successfully returns.

While we couldn't know the actual size of the shared memory segment we can do a trick and pass 0 as a size to shmget. Later shmctl could be used to check the actual size.

I wrote sample dummy programs that proves the thesis above.

Writer, creates shared memory object and writes argv[1] into it
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_KEY 0x0001b6e6

int main(int argc, char *argv[])
{
        if (argc < 2)
        {
                printf("usage: writer \"[data to write]\"\n");

                return 1;
        }

        int size = strlen(argv[1]);

        int shmid = shmget(SHM_KEY, size + 1, 0644 | IPC_CREAT);

        char *data = shmat(shmid, NULL, 0); 
        strncpy(data, argv[1], size);

        shmdt(data);

        fgetc(stdin);

        shmctl(shmid, IPC_RMID, NULL);

        return 0;
}

Reader, gets the shared object, checks the size and copies data from the shared memory segment:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_KEY 0x0001b6e6

int main(int argc, char *argv[])
{
        int shmid = shmget(SHM_KEY, 0, 0644 | IPC_CREAT);

        struct shmid_ds ds; 
        shmctl(shmid, IPC_STAT, &ds);
        printf("Size: \"%d\"\n", ds.shm_segsz);

        char *data = shmat(shmid, NULL, 0); 
        printf("Sata: \"%s\"\n", data);

        shmdt(data); 

        return 0;
}

The output of these programs:
$ ./writer "some text"

$ ipcs -m | awk '{if ($1 == "0x0001b6e6" || $1 == "key") {print $0}}'
key        shmid      owner      perms      bytes      nattch     status      
0x0001b6e6 11829291   niam      644        10         0

$ ./reader 
Size: "10"
Sata: "some text"
And for another input data for writer to insure that this works as expected:
$ ./writer "some longer text"

$ ipcs -m | awk '{if ($1 == "0x0001b6e6" || $1 == "key") {print $0}}'
key        shmid      owner      perms      bytes      nattch     status      
0x0001b6e6 11862059   niam      644        17         0

$ ./reader 
Size: "17"
Sata: "some longer text"

No comments: