I'm working on a simple FIFO queue to synchronize multiple instances of a server process.
This is very similar to
Linux synchronization with FIFO waiting queue, except dealing with multiple processes instead of threads. I adapted caf's ticket lock to use process-shared mutex and condition variable from a shared memory segment. It also handles timeouts in case one process dies while processing a request:
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
static inline void fail(char *str)
{
perror(str);
exit(1);
}
/***************************************************************************************************/
/* Simple ticket lock queue with pthreads
* https://stackoverflow.com/questions/3050083/linux-synchronization-with-fifo-waiting-queue
*/
typedef struct ticket_lock {
pthread_mutex_t mutex;
pthread_cond_t cond;
int queue_head, queue_tail;
} ticket_lock_t;
static void
ticket_init(ticket_lock_t *t)
{
pthread_mutexattr_t mattr;
pthread_mutexattr_init(&mattr);
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(&t->mutex, &mattr);
pthread_mutexattr_destroy(&mattr);
pthread_condattr_t cattr;
pthread_condattr_init(&cattr);
pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&t->cond, &cattr);
pthread_condattr_destroy(&cattr);
t->queue_head = t->queue_tail = 0;
}
static void
ticket_broadcast(ticket_lock_t *ticket)
{
pthread_cond_broadcast(&ticket->cond);
}
static int
ticket_lock(ticket_lock_t *ticket)
{
pthread_mutex_lock(&ticket->mutex);
int queue_me = ticket->queue_tail++;
while (queue_me > ticket->queue_head) {
time_t sec = time(NULL) + 5; /* 5s timeout */
struct timespec ts = { .tv_sec = sec, .tv_nsec = 0 };
fprintf(stderr, "%i: waiting, current: %i me: %i\n", getpid(), ticket->queue_head, queue_me);
if (pthread_cond_timedwait(&ticket->cond, &ticket->mutex, &ts) == 0)
continue;
if (errno != ETIMEDOUT) fail("pthread_cond_timedwait");
/* Timeout, kick current user... */
fprintf(stderr, "kicking stale ticket %i\n", ticket->queue_head);
ticket->queue_head++;
ticket_broadcast(ticket);
}
pthread_mutex_unlock(&ticket->mutex);
return queue_me;
}
static void
ticket_unlock(ticket_lock_t *ticket, int me)
{
pthread_mutex_lock(&ticket->mutex);
if (ticket->queue_head == me) { /* Normal case: we haven't timed out. */
ticket->queue_head++;
ticket_broadcast(ticket);
}
pthread_mutex_unlock(&ticket->mutex);
}
/***************************************************************************************************/
/* Shared memory */
#define SHM_NAME "fifo_sched"
#define SHM_MAGIC 0xdeadbeef
struct sched_shm {
int size;
int magic;
int ready;
/* sched stuff */
ticket_lock_t queue;
};
static unsigned int shm_size = 256;
static struct sched_shm *shm = 0;
/* Create new shared memory segment */
static void
create_shm()
{
int fd = shm_open(SHM_NAME, O_RDWR | O_CREAT | O_TRUNC, 0644);
assert(fd != -1);
int r = ftruncate(fd, shm_size); assert(r == 0);
void *pt = mmap(0, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
assert(pt != MAP_FAILED);
fprintf(stderr, "Created shared memory.\n");
shm = pt;
memset(shm, 0, sizeof(*shm));
shm->size = shm_size;
shm->magic = SHM_MAGIC;
shm->ready = 0;
ticket_init(&shm->queue);
shm->ready = 1;
}
/* Attach existing shared memory segment */
static int
attach_shm()
{
int fd = shm_open(SHM_NAME, O_RDWR, 0);
if (fd == -1) return 0; /* Doesn't exist yet... */
shm = mmap(0, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm == MAP_FAILED) fail("mmap");
fprintf(stderr, "Mapped shared memory.\n");
assert(shm->magic == SHM_MAGIC);
assert(shm->ready);
return 1;
}
static void
shm_init()
{
fprintf(stderr, "shm_init()\n");
assert(shm_size >= sizeof(struct sched_shm));
if (!attach_shm())
create_shm();
}
/***************************************************************************************************/
int main()
{
shm_init();
while (1) {
int ticket = ticket_lock(&shm->queue);
printf("%i: start %i\n", getpid(), ticket);
printf("%i: done %i\n", getpid(), ticket);
ticket_unlock(&shm->queue, ticket);
}
return 0;
}
This works well standalone and while adding extra processes:
$ gcc -g -Wall -std=gnu99 -o foo foo.c -lpthread -lrt
$ ./foo
$ ./foo # (in other term)
...
26370: waiting, current: 134803 me: 134804
26370: start 134804
26370: done 134804
26370: waiting, current: 134805 me: 134806
26370: start 134806
26370: done 134806
26370: waiting, current: 134807 me: 134808
However killing the 2nd instance breaks pthread_cond_timedwait() in the 1st:
pthread_cond_timedwait: No such file or directory
Which makes sense in a way, the condition variable was tracking this process and it's not there anymore.
Surely there must be a way to recover from this ?
[too long for a comment]
pthread_cond_timedwait: No such file or directory
Hu! :-)
The pthread_*() family of functions does not set errno to any error code but returns it.
So to get any usable results change this
if (pthread_cond_timedwait(&ticket->cond, &ticket->mutex, &ts) == 0)
continue;
if (errno != ETIMEDOUT) fail("pthread_cond_timedwait");
to be
if ((errno = pthread_cond_timedwait(&ticket->cond, &ticket->mutex, &ts)) == 0)
continue;
if (errno != ETIMEDOUT) fail("pthread_cond_timedwait");
Ok, quoting posix pthread_mutex_lock() reference:
If mutex is a robust mutex and the process containing the owning thread terminated while holding the mutex lock, a call to pthread_mutex_lock() shall return the error value [EOWNERDEAD]. [...] In these cases, the mutex is locked by the thread but the state it protects is marked as inconsistent. The application should ensure that the state is made consistent for reuse and when that is complete call pthread_mutex_consistent(). If the application is unable to recover the state, it should unlock the mutex without a prior call to pthread_mutex_consistent(), after which the mutex is marked permanently unusable.
So in addition to alk's comment to robustly handle processes dying with the mutex locked we need to watch for EOWNERDEAD when calling pthread_mutex_lock() and pthread_cond_timedwait(), and call pthread_mutex_consistent() on it.
Something like:
if ((errno = pthread_cond_timedwait(&ticket->cond, &ticket->mutex, &ts)) == 0)
continue;
if (errno == EOWNERDEAD) /* Recover mutex owned by dead process */
pthread_mutex_consistent(&ticket->mutex);
else if (errno != ETIMEDOUT)
fail("pthread_cond_timedwait");
Related
I have a multiclient Server Socket and I want to block the acces for other Clients, when sending "BEG" to the Server. To open the other Clients again, the Client has to send "END" to the Server. While other Clients are blocked off, they only can use "quit" to exit the Server and if they use conditions() they fall asleep.
So other Clients are blocked for the function conditions() if one process used "BEG", but the process himself has still acces to the function.
If I compile my Code, the Server is running, everything is fine but the Mutexe doesn't work.
The Code is going into the if statement of "BEG" and the Mutex should be locked, but other Clients aren't blocked off.
If I connect a second Client, the Client gets kicked if I use conditions().
My question is, why does the mutex not work for other Clients or in generell? How to check if the Mutex is working?
Edit:
Now my Semaphore doesn't block other processes
Edit 2: I found a way, not the best but one. Now some clients are getting kicked from the Server after using one condition().
main.c:
int state = 0;
int beg() {
state = 1;
return 0;
}
int end() {
state = 0;
return 0;
}
int main() {
int pid, t;
char *eingabe, *inputBuffer[BUFSIZE];
char delimiter[] = "\n ";
int rfd = erstelleSocket();
int cfd;
semaphor semID1 = semGET();
semaphor semID2 = semGET2();
marker[0] = 1;
t = semctl(semID1, 1, SETALL, marker);
if (t == -1) {
fprintf(stderr, "Error with marker\n");
}
t = semctl(semID2, 1, SETALL, marker);
if (t == -1) {
fprintf(stderr, "Error with marker\n");
}
while(1){
cfd = accept(rfd, (struct sockaddr *) &client, &client_len);
if (cfd < 0) {
close(cfd);
fprintf(stderr, "connection failed\n");
break;
}
pid = fork();
if (pid < 0) {
fprintf(stderr, "Error in new process creation\n");
}
if (pid == 0) {
bzero(input, sizeof(input));
bytes_read = read(cfd, input, BUFSIZE);
strncat(input, " ", strlen(" "));
input[strcspn(input, "\r\n")] = 0;
while (bytes_read > 0) {
eingabe = strtok(input, delimiter);
int i = 0;
while (eingabe != NULL) {
inputBuffer[i++] = eingabe;
eingabe = strtok(NULL, delimiter);
}
if (strncmp("quit", inputBuffer[0], 4) == 0) {
close(cfd);
break;
}
if (state != 1) {
down(semID2, 0); //down is a function with semop()
}
down(semID1, 0);
conditions(inputBuffer[0],
inputBuffer[1],
inputBuffer[2],
cfd, semID1, shmID);
up(semID1, 0);
if (state != 1) {
up(semID2, 0); //up is a function with semop()
}
bzero(input, sizeof(input));
bytes_read = read(cfd, input, BUFSIZE);
strncat(input, " ", strlen(" "));
input[strcspn(input, "\r\n")] = 0;
close(rfd);
}
}
close(cfd);
}
close(rfd);
}
my condition function:
void conditions(char *eingabehalter1,
char *eingabehalter2,
char *eingabehalter3,
int cfd, int shmID) {
if (strncmp("PUT", eingabehalter1, 3) == 0) {
put(eingabehalter2, eingabehalter3, cfd, shmID);
} else if (strncmp("GET", eingabehalter1, 3) == 0) {
get(eingabehalter2, cfd, shmID);
} else if (strncmp("DEL", eingabehalter1, 3) == 0) {
del(eingabehalter2, cfd, shmID);
} else if (strncmp("BEG", eingabehalter1, 3) == 0) {
beg();
} else if (strncmp("END", eingabehalter1, 3) == 0) {
end();
} else {
write(cfd, "cmd_nonexistent\n", strlen("cmd_nonexistent\n"));
}
}
createSocket.c:
int rfd; // Rendevouz-Descriptor
rfd = socket(AF_INET, SOCK_STREAM, 0);
int option = 1;
setsockopt(rfd,SOL_SOCKET, SO_REUSEADDR, (const void *) &option, sizeof(int));
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(PORT);
int brt = bind(rfd, (struct sockaddr *) &server, sizeof(server));
int lrt = listen(rfd, 5);
return rfd;
}
main.h:
#include "shmmemory.h"
#include "semaphoren.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 5678
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t client_len;
char input[BUFSIZE];
int bytes_read;
int erstelleSocket();
void conditions(char *eingabehalter1,
char *eingabehalter2,
char *eingabehalter3,
int cfd, int shmID);
int beg();
int end();
unsigned short marker[2];
Your approach cannot work because you're trying to combine fork with threads. fork creates a copy of the parent's address space for each child process, which means that each child process has its own copy of the mutex object. Process-shared mutexes are possible in POSIX, with special attributes, but I suspect even those don't work with fork; they have to be placed in shared memory.
Have you considered creating threads with pthread_create for the service loop? Or else you can implement this entirely using fork (no pthread material). The children can use POSIX named semaphores (sem_open, et al) or possibly, dare I say it, System V IPC.
Also, don't use strtok in multithreaded code, and clearing memory to zero was standardized in 1989's ANSI C as memset(pointer, 0, size). Since that was 31 years ago, it's okay to lay bzero to rest.
The way you initialize the semaphores is wrong for your use case. From the man page of sem_init():
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
If pshared has the value 0, then the semaphore is shared between the
threads of a process, and should be located at some address that is
visible to all threads (e.g., a global variable, or a variable
allocated dynamically on the heap).
If pshared is nonzero, then the semaphore is shared between
processes, and should be located in a region of shared memory
Based on the above explanations from the man page, the things you need change are:
Semaphore declaration
Since you are using semaphores between processes, you need to declare the variable as shared. You can do that via mmap() to create unnamed UNIX semaphore as follows:
sem_t* sem_var = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)
if (sem_var == MAP_FAILED) // Shared memory creation failed.
goto handle_shm_fail;
Semaphore initialization
Since you are using POSIX semaphores, to make your child use the same semaphores, pshared is set to 1 indicating semaphore is shared between processes.
if (sem_init(sem_var, 1, 1) != 0) // Semaphore initialization failed.
goto handle_sem_fail;
NOTE: In your code sem_var is of type sem_t, now it is a pointer to sem_t. Accordingly, you need to update your code.
I usually code C in linux. I am using now a Mac and I am new on this machine.
In linux when I use shared memory between process, the memory is allocated as a file which pathname is /dev/shm/resource_name.
I was trying a simple code and suddenly I got an error.
It wasn't able to call a function destroy() to destroy the shared memory.
Usually when this happens I delete the file manually on the directory.
My question is: Where is located the shared memory in OS X. Because when I try recompile and execute, the gcc compiler tells me that the resource already exists and I don't know how to delete it.
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
int increment (int n)
{
n = n + 1;
printf ("%d\n", n);
return n;
}
int *create ()
{
int *ptr;
int ret;
int fd= shm_open ("/shm", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror ("shm");
exit (1);
}
ret = ftruncate (fd, sizeof (int));
if (ret == -1) {
perror ("shm");
exit (2);
}
ptr = mmap (0, sizeof (int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror ("shm-mmap");
exit (3);
}
return ptr;
}
void destroy (int *ptr)
{
int ret;
ret = munmap (ptr, sizeof (int));
if (ret == -1) {
perror ("shm");
exit (7);
}
ret = shm_unlink ("shm");
if (ret == -1) {
perror ("shm");
exit (8);
}
}
int main (int argc, char *argv[])
{
sem_t *semaphore;
int *ptr = create ();
int numProcessesChilds, numIncrements;
int i;
if (argc == 3) {
numProcessesChilds = atoi (argv [1]);
numIncrements = atoi (argv [2]);
}
else {
numProcessesChilds = 10;
numIncrements = 1;
}
*ptr = 0;
semaphore = sem_open("/semaphore", O_CREAT, 0xFFFFFFFF, 1);
if (semaphore == SEM_FAILED) {
perror("semaphore");
}
for (i = 0; i < numProcessesChilds; i++) {
switch (fork ()) {
case -1:
perror ("fork");
exit (1);
case 0:
sem_wait(semaphore);
for (i = 0 ; i < numIncrements; i++) {
(*ptr) = increment (*ptr);
}
sem_post(semaphore);
exit (0);
}
}
for (i = 0; i < numProcessesChilds; i++) {
wait (NULL);
}
sem_close(semaphore);
sem_unlink("/semaphore");
printf ("Fina value: %d\n", *ptr);
destroy (ptr);
return 0;
}
Answered here, Mac OS being derived from BSD does not expose any entry in the file system for shared memory objects. The corresponding files in /dev/shm are Linux specific.
Under Mac OS, only shm_unlink() will do the cleanup job. The OP's example program should work upon each startup as it passes O_CREAT flag to shm_open(). If the shared memory object does not already exist, it is created otherwise it is opened as it is. As the resulting memory area pointed by ptr is reset with the instruction *ptr = 0 at the beginning of the program, everything should work properly.
Mac OS X , like linux is UNIX based so it handles the shared memory just like Linux. The shared memory segments you allocate are also files located in /dev/shm
To destroy the shared memory you can use the command ipcs -m or ipcs -M to view all the shared memory, look for yours and then execute ipcrm -m shmid where shmid would be the id of your shared memory. You can also do ipcrm -M shmkey using the key you assigned to it
The following code shows a producer-consumer example:
Once a product is produced, the consumer will get this product.
But I'm surprised that the consumer will sill get a product when there is no product.
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#define NUM 5
int queue[NUM];
int i;
sem_t *blank_number, *product_number;
void *producer(void *arg) {
int p = 0;
while (1) {
sem_wait(blank_number);
queue[p] = rand() % 1000 + 1;
printf("Produce queue[%d]:%d\n", p, queue[p]);
i = sem_post(product_number);
//printf("i_p=%d\n", i);
p = (p+1)%NUM;
sleep(rand()%5);
}
}
void *consumer(void *arg) {
int c = 0;
while (1) {
sem_wait(product_number);
printf("Consume queue[%d]:%d\n", c, queue[c]);
queue[c] = 0;
i = sem_post(blank_number);
//printf("i_c=%d\n", i);
c = (c+1)%NUM;
sleep(rand()%5);
}
}
int main(int argc, char *argv[]) {
pthread_t pid, cid;
//set blank_number to NUM
blank_number = sem_open("blank_number", O_CREAT, S_IRWXU, NUM);
if(blank_number == SEM_FAILED){
perror("open blank_number");
return 1;
}
//set product_number to 0
product_number = sem_open("product_number", O_CREAT, S_IRWXU, 0);
if(product_number == SEM_FAILED){
perror("open product_number");
return 1;
}
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
sem_close(blank_number);
sem_close(product_number);
return 0;
}
In my test result, there is only one product: 808, but the consumer gets two products: 808 and 0;
$ sudo ./a.out
Produce queue[0]:808
Consume queue[0]:808
Consume queue[1]:0
Is there any wrong in my code?
Your problem is that you never deleted your semaphores. So when you open them you recover some old/bad state. Try to open with O_EXCL you will be able to observe the problem.
Write a simple command to delete them with sem_unlink() or initialize them before using them with semctl.
You also need to set the appropriate values in sem_open not 022...
Alos note that POSIX named semaphores should have a name starting with /.
Change the beginning of your main to :
sem_unlink("blank_number");
sem_unlink("product_number");
//set blank_number to 1
blank_number = sem_open("blank_number", O_CREAT|O_EXCL, S_IRWXU, 1);
if(blank_number == SEM_FAILED){
perror("open blank_number");
return 1;
}
//set product_number to 0
product_number = sem_open("product_number", O_CREAT|O_EXCL, S_IRWXU, 0);
if(product_number == SEM_FAILED){
perror("open product_number");
return 1;
}
Maybe try to use sem_init with an unnamed semaphore instead of sem_open:
sem_t semaphore;
int ret = sem_init(&semaphore, 0, 0);
I am learning "Advanced Programming in Unix Environment", and have a problem with exercise no.17 in chapter 15.
The exercise ask reader to "use advisory recordlocking to alternate between the parent and the child".
And i found that it can not be done without using usleep(). There is nothing to stop kernel continually scheduling parent after unlocking the record, and vice versa.
Does anyone has the answer? Thanks in advance.
HERE is my code:
ex17.c
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/wait.h>
#include "ch14/lock.h"
#define NLOOPS 1000
#define SIZE sizeof(long) /* size of shared memory area */
static int update(long *ptr)
{
return (*ptr)++; /* return value before increment */
}
int main(int argc, char* argv[])
{
int fd, counter, lockFd;
pid_t pid;
void *area;
if((fd = open("/dev/zero", O_RDWR)) < 0)
{
perror("open error");
exit(1);
}
if((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED)
{
perror("mmap error");
exit(1);
}
close(fd); /*can close /dev/zero now that it's mapped */
if((lockFd = open("ex17.lock", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0)
{
perror("open error");
exit(-1);
}
/* insure parent runs first */
writew_lock(lockFd, 0, SEEK_SET, 1); /* first byte in the lock file represent parent */
writew_lock(lockFd, 1, SEEK_SET, 1); /* second byte in the lock file represent child */
if((pid = fork()) < 0)
{
perror("fork error");
exit(1);
}else if(pid > 0){ /* parent */
for(int i = 0; i<NLOOPS; i+=2)
{
writew_lock(lockFd, 0, SEEK_SET, 1);
readw_lock(lockFd, 1, SEEK_SET, 1);
if((counter = update((long*)area)) != i)
{
fprintf(stderr, "parent: expected %d, got %d\n", i, counter);
exit(1);
}else{
printf("%s got %d\n", "parent", counter);
}
un_lock(lockFd, 1, SEEK_SET, 1);
usleep(1000); /*if without this line, there is nothing to stop kernel continually scheduling parent.*/
readw_lock(lockFd, 0, SEEK_SET, 1);
}
waitpid(pid, NULL, 0);
} else {
for(int i=1; i<NLOOPS+1; i+=2)
{
writew_lock(lockFd, 1, SEEK_SET, 1);
readw_lock(lockFd, 0, SEEK_SET, 1);
if((counter = update((long*)area)) != i)
{
fprintf(stderr, "child: expected %d, got %d\n", i, counter);
exit(1);
}else{
printf("%s got %d\n", "child", counter);
}
un_lock(lockFd, 0, SEEK_SET, 1);
usleep(1000);
readw_lock(lockFd, 1, SEEK_SET, 1);
}
}
exit(0);
}
ch14/lock.h
#ifndef CH14_LOCK_H_INCLUDED
#define CH14_LOCK_H_INCLUDED
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int lock_reg(int, int, int, off_t, int, off_t);
pid_t lock_test(int, int, off_t, int, off_t);
#define read_lock(fd, offset, whence, len) lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lock(fd, offset, whence, len) lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))
#define is_read_lockable(fd, offset, whence, len) (lock_test((fd), F_RDLCK, (offset), (whence), (len)) == 0)
#define is_write_lockable(fd, offset, whence, len) (lock_test((fd), F_WRLCK, (offset), (whence), (len)) == 0)
#endif // CH14_LOCK_H_INCLUDED
ch14/lock.c
#include "lock.h"
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */
lock.l_whence = whence; /* byte offset, relative to l_whence */
lock.l_start = offset; /* byte offset, relative to l_whence */
lock.l_len = len; /* #bytes (0 means to EOF) */
return fcntl(fd, cmd, &lock);
}
pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK OR F_WRLCK */
lock.l_start = offset; /* byte offset, relative to l_whence */
lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
lock.l_len = len; /* #bytes (0 means to EOF) */
if(fcntl(fd, F_GETLK, &lock) < 0)
{
perror("fcntl error");
exit(-1);
}
if(lock.l_type == F_UNLCK)
{
return 0; /* false, region isn't locked by another proc */
}else{
return lock.l_pid; /* true, return pid of lock owner */
}
}
Advisory lock implementations differ from one unix-like OS to another, but they usually are not mandatory by default, and this is the case with linux. man fcntl contains a section for advisory locks and mandatory locks; from the beginning of the latter (where "above" refers to the former):
The above record locks may be either advisory or mandatory, and are advisory by default. Advisory locks are not enforced and are useful only between cooperating processes.
This means in order for the lock to be effective, each cooperating process must check to see if it can acquire one before it does so. man fcntl says some things about this for the F_GETLK call:
F_GETLK (struct flock *)
On input to this call, lock describes a lock we would like to place on the file. If the lock could be placed, fcntl() does not actually place it, but returns F_UNLCK in the l_type field of lock and leaves the other fields of the structure unchanged. If one or more incompatible
locks would prevent this lock being placed, then fcntl() returns details about one of these locks in the l_type, l_whence, l_start, and l_len fields of lock and sets l_pid to be the PID of the process holding that lock.
So the struct you submit using F_GETLK may have some fields changed to indicate a result -- this is what you need to check. If it succeeds, you can then call with F_SETLK to actually apply the lock.1 If it doesn't succeed, you need to wait until it does, and none of these calls are blocking, meaning they return immediately regardless of the situation. This is where the necessity of a short sleep comes in, because if you simply loop around checking the lock over and over again, you will be busy looping a processor (maxing it out doing nothing). However, if you throw in a 5 or 10 ms passive delay, the loop will be mostly doing nothing passively (i.e., without maxing out a processor). This delay should be only after a failed check; if the check succeeds, immediately set the lock.
All that can go in a single function. It does not take care of getting the processes to steadily alternate, but this is actually sort of a contrived criteria: if you wanted to accomplish that goal in "real life", you would not go about it this way. However, it's not impossible; the first thing I would try would be to use a sleep at the beginning of the function which is several times longer than the one in the loop, so that a process which releases a lock and then goes to acquire it again will be delayed longer than a process which was concurrently trying to get it in the check/set loop.
1. Unfortunately, that implies a potential race condition whereby one process may set a lock in between another process's check and set calls -- a weakness of this system.
I have a bit of an issue with one of my projects.
I have been trying to find a well documented example of using shared memory with fork() but to no success.
Basically the scenario is that when the user starts the program, I need to store two values in shared memory: current_path which is a char* and a file_name which is also char*.
Depending on the command arguments, a new process is kicked off with fork() and that process needs to read and modify the current_path variable stored in shared memory while the file_name variable is read only.
Is there a good tutorial on shared memory with example code (if possible) that you can direct me to?
There are two approaches: shmget and mmap. I'll talk about mmap, since it's more modern and flexible, but you can take a look at man shmget (or this tutorial) if you'd rather use the old-style tools.
The mmap() function can be used to allocate memory buffers with highly customizable parameters to control access and permissions, and to back them with file-system storage if necessary.
The following function creates an in-memory buffer that a process can share with its children:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
void* create_shared_memory(size_t size) {
// Our memory buffer will be readable and writable:
int protection = PROT_READ | PROT_WRITE;
// The buffer will be shared (meaning other processes can access it), but
// anonymous (meaning third-party processes cannot obtain an address for it),
// so only this process and its children will be able to use it:
int visibility = MAP_SHARED | MAP_ANONYMOUS;
// The remaining parameters to `mmap()` are not important for this use case,
// but the manpage for `mmap` explains their purpose.
return mmap(NULL, size, protection, visibility, -1, 0);
}
The following is an example program that uses the function defined above to allocate a buffer. The parent process will write a message, fork, and then wait for its child to modify the buffer. Both processes can read and write the shared memory.
#include <string.h>
#include <unistd.h>
int main() {
char parent_message[] = "hello"; // parent process will write this message
char child_message[] = "goodbye"; // child process will then write this one
void* shmem = create_shared_memory(128);
memcpy(shmem, parent_message, sizeof(parent_message));
int pid = fork();
if (pid == 0) {
printf("Child read: %s\n", shmem);
memcpy(shmem, child_message, sizeof(child_message));
printf("Child wrote: %s\n", shmem);
} else {
printf("Parent read: %s\n", shmem);
sleep(1);
printf("After 1s, parent read: %s\n", shmem);
}
}
Here is an example for shared memory :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024 /* make it a 1K shared memory segment */
int main(int argc, char *argv[])
{
key_t key;
int shmid;
char *data;
int mode;
if (argc > 2) {
fprintf(stderr, "usage: shmdemo [data_to_write]\n");
exit(1);
}
/* make the key: */
if ((key = ftok("hello.txt", 'R')) == -1) /*Here the file must exist */
{
perror("ftok");
exit(1);
}
/* create the segment: */
if ((shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT)) == -1) {
perror("shmget");
exit(1);
}
/* attach to the segment to get a pointer to it: */
if ((data = shmat(shmid, NULL, 0)) == (void *)-1) {
perror("shmat");
exit(1);
}
/* read or modify the segment, based on the command line: */
if (argc == 2) {
printf("writing to segment: \"%s\"\n", argv[1]);
strncpy(data, argv[1], SHM_SIZE);
} else
printf("segment contains: \"%s\"\n", data);
/* detach from the segment: */
if (shmdt(data) == -1) {
perror("shmdt");
exit(1);
}
return 0;
}
Steps :
Use ftok to convert a pathname and a project identifier to a System V IPC key
Use shmget which allocates a shared memory segment
Use shmat to attache the shared memory segment identified by shmid to the address space of the calling process
Do the operations on the memory area
Detach using shmdt
These are includes for using shared memory
#include<sys/ipc.h>
#include<sys/shm.h>
int shmid;
int shmkey = 12222;//u can choose it as your choice
int main()
{
//now your main starting
shmid = shmget(shmkey,1024,IPC_CREAT);
// 1024 = your preferred size for share memory
// IPC_CREAT its a flag to create shared memory
//now attach a memory to this share memory
char *shmpointer = shmat(shmid,NULL);
//do your work with the shared memory
//read -write will be done with the *shmppointer
//after your work is done deattach the pointer
shmdt(&shmpointer, NULL);
try this code sample, I tested it, source: http://www.makelinux.net/alp/035
#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main ()
{
int segment_id;
char* shared_memory;
struct shmid_ds shmbuffer;
int segment_size;
const int shared_segment_size = 0x6400;
/* Allocate a shared memory segment. */
segment_id = shmget (IPC_PRIVATE, shared_segment_size,
IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
/* Attach the shared memory segment. */
shared_memory = (char*) shmat (segment_id, 0, 0);
printf ("shared memory attached at address %p\n", shared_memory);
/* Determine the segment's size. */
shmctl (segment_id, IPC_STAT, &shmbuffer);
segment_size = shmbuffer.shm_segsz;
printf ("segment size: %d\n", segment_size);
/* Write a string to the shared memory segment. */
sprintf (shared_memory, "Hello, world.");
/* Detach the shared memory segment. */
shmdt (shared_memory);
/* Reattach the shared memory segment, at a different address. */
shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0);
printf ("shared memory reattached at address %p\n", shared_memory);
/* Print out the string from shared memory. */
printf ("%s\n", shared_memory);
/* Detach the shared memory segment. */
shmdt (shared_memory);
/* Deallocate the shared memory segment. */
shmctl (segment_id, IPC_RMID, 0);
return 0;
}
Here's a mmap example:
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
* pvtmMmapAlloc - creates a memory mapped file area.
* The return value is a page-aligned memory value, or NULL if there is a failure.
* Here's the list of arguments:
* #mmapFileName - the name of the memory mapped file
* #size - the size of the memory mapped file (should be a multiple of the system page for best performance)
* #create - determines whether or not the area should be created.
*/
void* pvtmMmapAlloc (char * mmapFileName, size_t size, char create)
{
void * retv = NULL;
if (create)
{
mode_t origMask = umask(0);
int mmapFd = open(mmapFileName, O_CREAT|O_RDWR, 00666);
umask(origMask);
if (mmapFd < 0)
{
perror("open mmapFd failed");
return NULL;
}
if ((ftruncate(mmapFd, size) == 0))
{
int result = lseek(mmapFd, size - 1, SEEK_SET);
if (result == -1)
{
perror("lseek mmapFd failed");
close(mmapFd);
return NULL;
}
/* Something needs to be written at the end of the file to
* have the file actually have the new size.
* Just writing an empty string at the current file position will do.
* Note:
* - The current position in the file is at the end of the stretched
* file due to the call to lseek().
* - The current position in the file is at the end of the stretched
* file due to the call to lseek().
* - An empty string is actually a single '\0' character, so a zero-byte
* will be written at the last byte of the file.
*/
result = write(mmapFd, "", 1);
if (result != 1)
{
perror("write mmapFd failed");
close(mmapFd);
return NULL;
}
retv = mmap(NULL, size,
PROT_READ | PROT_WRITE, MAP_SHARED, mmapFd, 0);
if (retv == MAP_FAILED || retv == NULL)
{
perror("mmap");
close(mmapFd);
return NULL;
}
}
}
else
{
int mmapFd = open(mmapFileName, O_RDWR, 00666);
if (mmapFd < 0)
{
return NULL;
}
int result = lseek(mmapFd, 0, SEEK_END);
if (result == -1)
{
perror("lseek mmapFd failed");
close(mmapFd);
return NULL;
}
if (result == 0)
{
perror("The file has 0 bytes");
close(mmapFd);
return NULL;
}
retv = mmap(NULL, size,
PROT_READ | PROT_WRITE, MAP_SHARED, mmapFd, 0);
if (retv == MAP_FAILED || retv == NULL)
{
perror("mmap");
close(mmapFd);
return NULL;
}
close(mmapFd);
}
return retv;
}