Answer the question
In order to leave comments, you need to log in
Synchronization in C or how output to a file differs from output to stdout
Hi all!
The university asked me to write a program in C. The bottom line is this:
./lastmsg [-i id] [-c|-d] ["Message"]
If the -c flag is passed, then a new memory segment (Shared memory) is created. If “Message” is passed, then the message that was written to the shared memory is displayed on stdout, and then it is replaced by the Message that was passed to the program. The idea is that you need to synchronize access to this segment. The -i flag sets the segment id - this is not particularly important in this case. It looks like the program was written correctly. The following code always prints letters three times a, b, c
(and 1234
once):
#!/bin/sh
./lastmsg -c 1234
(
for i in `seq 3`; do ./lastmsg a & ./lastmsg b & ./lastmsg c & done; wait
./lastmsg -d
)
#!/bin/sh
./lastmsg -c 1234
(
for i in `seq 3`; do ./lastmsg a & ./lastmsg b & ./lastmsg c & done; wait
./lastmsg -d
) > temp
grep -c a temp
grep -c b temp
grep -c c temp
rm -f temp
/**
* @file lastmsg.c
* @author Ivan Pryakin
* @date 10.12.2013
*
* @brief Main file
*
* @details This program takes creates a new shared memory segement. Its key can be specified with -i option, by default it is equal
* to student id. If -c option is given, it creates a new shared memory segment and attaches structure 'myshm' to it.
* If [Message] is specified it writes this string to shared memory segment, but before outputs whatever string that was
* there before + new line character. If -d option is specified, it deattaches and removes memory segment and outputs
* whatever string that was in there before.
**/
#include <stdio.h>
#include <sys/shm.h>
#include <seqev.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <signal.h>
#define MAX_DATA (50) /**< maximum length of a message */
#define PERMISSION (0600) /**< permissions for shared memory segment */
#define DEFAULT_KEY (1028223) /**< default key for shared memory segment */
static char* program; /**< program name (used only for usage() function) */
struct myshm {
/* state:
* false = OK
* true = TERMINATING
*
* Its purpose is to avoid 'race conditions', i.e. when two cuncurrent processes receive SIGINT/SIGQUIT signal,
* only one of them has to free resources.
*/
bool state;
char data[MAX_DATA];
};
/* holds identifiers for sharedmemory segment/event counter/sequencer */
struct shm_keys
{
int shmid;
eventcounter_t *event;
sequencer_t *sequencer;
};
/* has two structures - one that holds data and one that holds IDs */
struct t_sharedmemory
{
struct myshm *shared;
struct shm_keys keys;
};
struct t_sharedmemory flags;
/**
* @brief this function writes how to use this program.
* @details it is executed when an unknown option is given or no <cmd> is given. Global variable: program
*/
static void usage(void);
/**
* @brief free resources
* @details removes shared memory segment, destroys event counter/sequencer
*/
void free_resources(void);
/**
* @brief signal handling
* @details when any of the two signals arrive (SIGINT or SIGQUIT), this function will be called
* it frees resources and quits a program with EXIT_FAILURE return value
* @param signum - not used
*/
void sig_handler(int signum);
/**
* @brief create/get shared memory segment, output the string in memory segement, overwrite it with new string
* @details creates a new shared memory segement. Its key can be specified with -i option, by default it is equal
* to student id. If -c option is given, it creates a new shared memory segment and attaches structure 'myshm' to it.
* If [Message] is specified it writes this string to shared memory segment, but before outputs whatever string that was
* there before + new line character. If -d option is specified, it deattaches and removes memory segment and outputs
* whatever string that was in there before. Main function uses declared structure with name 'flags'.
*/
int main (int argc, char **argv)
{
/* if SIGINT or SIGQUIT signal arrives, call function 'sig_handler' */
signal(SIGINT, sig_handler);
signal(SIGQUIT, sig_handler);
program = argv[0];
/* keys for shared memory segment/event counter/sequencer */
key_t shm_id = DEFAULT_KEY;
key_t evn_id = DEFAULT_KEY+1;
key_t seq_id = DEFAULT_KEY+2;
/* message to be written to shared memory segment*/
char message[MAX_DATA];
/* for syncronization */
int ticket = -1;
/* for options/argument handling */
char c;
char *end;
bool create = false;
bool delete = false;
/* options/argument handling */
while ((c = getopt (argc, argv, "i:cd")) != -1)
{
switch (c)
{
case 'i':
shm_id = (key_t) strtol(optarg, &end, 10);
evn_id = (key_t) (shm_id + 1);
seq_id = (key_t) (evn_id + 1);
if (*end)
{
(void)fprintf(stderr, "%s: error converting -i argument to long integer\n", program);
return EXIT_FAILURE;
}
break;
case 'c':
create = true;
break;
case 'd':
delete = true;
break;
case '?':
usage();
return EXIT_FAILURE;
default:
assert(0);
}
}
if (create && delete)
{
usage();
return EXIT_FAILURE;
}
/* on delete no message should be written to shared memory */
if (delete)
{
message[0] = '\0';
}
else if (optind != argc) /* if message was given, copy it to variable 'message'*/
{
strncpy(message, argv[optind], MAX_DATA-1);
if (strlen(argv[optind]) > MAX_DATA-1)
message[MAX_DATA] = '\0';
}
if (!(create || delete) && message[0] == '\0')
{
usage();
return EXIT_FAILURE;
}
/* get a shared memory region */
flags.keys.shmid = shmget (shm_id, sizeof(*flags.shared), IPC_CREAT | PERMISSION);
if (flags.keys.shmid == -1)
{
(void)fprintf(stderr, "%s: error creating shared memory segment\n", program);
return EXIT_FAILURE;
}
/* attach shared memory segment to the address space of the calling process */
flags.shared = shmat(flags.keys.shmid, NULL, 0);
if (flags.shared == (struct myshm *)-1)
{
(void)fprintf(stderr, "%s: error attaching memory segment\n", program);
return EXIT_FAILURE;
}
/* create event counter */
flags.keys.event = create_eventcounter(evn_id);
if (flags.keys.event == NULL)
{
(void)fprintf(stderr, "%s: error creating eventcounter\n", program);
return EXIT_FAILURE;
}
/* create sequencer */
flags.keys.sequencer = create_sequencer(seq_id);
if (flags.keys.sequencer == NULL)
{
(void)fprintf(stderr,"%s: error creating sequencer\n", program);
return EXIT_FAILURE;
}
/* get a ticket and wait if neccessary */
ticket = sticket(flags.keys.sequencer);
eawait(flags.keys.event, ticket);
if (!create)
{
fflush(stdout);
(void)fprintf(stdout, "%s\n", flags.shared->data);
}
if ((message[0] != '\0') && (!delete))
strcpy(flags.shared->data, message);
/* critical section end */
/* advance eventcounter, so that other concurrent process (that is currently waiting)
* can start execution of critical section
*/
if (delete)
{
free_resources();
return EXIT_SUCCESS;
}
//(void) fprintf(stderr,"eventcounter=%ld ticket=%d message=%s\n\n", eread(flags.keys.event), ticket, message);
eadvance(flags.keys.event);
return EXIT_SUCCESS;
}
static void usage(void)
{
(void) fprintf(stderr, "Usage: %s [-i id] [-c|-d] [\"Message\"]\n", program);
}
void free_resources(void)
{
/* detach from shared memory */
if (shmdt(flags.shared) == -1)
{
(void)fprintf(stderr, "%s: error detaching shared memory segment\n", program);
exit(EXIT_FAILURE);
}
/* mark segment to be destroyed */
if (shmctl(flags.keys.shmid, IPC_RMID, NULL) == -1)
{
(void)fprintf(stderr, "%s: error removing shared memory segment\n", program);
exit(EXIT_FAILURE);
}
/* remove event counter */
if (rm_eventcounter(flags.keys.event) == -1)
{
(void)fprintf(stderr, "%s: error removing event counter\n", program);
exit(EXIT_FAILURE);
}
/* remove sequence counter*/
if (rm_sequencer(flags.keys.sequencer) == -1)
{
(void)fprintf(stderr, "%s: error removing sequencer\n", program);
exit(EXIT_FAILURE);
}
}
void sig_handler(int signum)
{
/* avoid race conditions */
if (flags.shared->state) return;
flags.shared->state = true;
/* free resources and exit with error */
free_resources();
(void) fprintf(stderr, "%s:signal caught...terminating\n", program);
exit(EXIT_FAILURE);
}
CC = gcc
CFLAGS = -std=c99 -Wall -g -pedantic -DENDEBUG -D_BSD_SOURCE -D_XOPEN_SOURCE=500
LFLAGS = -lseqev
all: lastmsg
lastmsg: lastmsg.c
$(CC) $(CFLAGS) -o lastmsg lastmsg.c $(LFLAGS)
clean:
rm -f lastmsg
fflush(stdout);
did not help, but output to stderr (instead of stdout) brought results - when `seq 3` syncs fine (3 3 3 every time), but at `seq 1000` there is already a discrepancy (970 980 970 are about the same numbers). It follows that the error is still in the program. Did I understand correctly? Answer the question
In order to leave comments, you need to log in
1. Standard output is no different from other files. Actually, the standard output can be any file, terminal, pipe, socket ... etc. This is at the level of the "axis".
2. But your “problem” is not at the axis level, but at the FILE* abstraction level from “stdio.h”. Your problem is the "problem" of I/O buffering. The input / output that "stdio.h" does not go directly, but through the buffer. Which can be "no buffering" / "line buffering" / "full buffering". And this is not a bug but a feature, buffering can increase input output performance quite a bit.
When the runtime library initializes stdin,stdout,stderr for descriptors 0,1,2 it looks at the file types (asks the axis). And if it is a “terminal”, then it includes line buffering (i.e., the write() system call actually occurs when the line is filled up to '\n'), and if it is a “file”, then it is full (i.e., until the buffer is filled in the file is not reset, or by an explicit call to fflush(sdtout);).
3. Actually, explicit execution will help you:
setlinebuf(stdout); // Включаем построчную буферизацию, как для терминала
> then this is the only way to check it (well, don’t count 3000 characters manually?)
copy it from the console, save it to a file, and check it ...
Try to open a file in a shell script not through ">", but through ">>", making sure that the file does not exist before the operation.
It's possible that various processes somehow get unlinked copies of the file descriptor where the position in the file isn't updated when printing from other processes; then process 1 can print its letter a, and then process 2 prints its letter b at the same position in the file, overwriting the a from the first process. There is no such problem with the console, because it is not seekable, it does not have a position as such. Opening a file for appending has the same goal - before each write operation, the position will be set to the end of the file.
There are two problems:
- if you run the script as is, the total number of lines in temp is not always equal to 3 * n + 1. This is a problem of writing to one file without O_APPEND. Solved by replacing > temp with >> temp, as suggested by CleverMouse
- even when the total number of lines is correct, the number of occurrences of different characters is different. This is clearly a code issue, either lastmsg or libseqev.
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question