Привет всем!
В университете задали написать программу на Си. Суть такова:
./lastmsg [-i id] [-c|-d] ["Message"]
Если передаётся флаг -c, то создаётся новый сегмент памяти (Shared memory). Если передается «Message», то на stdout выводится то сообщение, которое было записано в shared memory, а потом оно заменятеся тем Message, который передался программе. Идея в том, что надо синхронизировать доступ в этот сегмент. Флаг -i задает id сегмента — это не особо важно в данном случае. Вроде программу написал правильно. Следующий код
всегда выводит по три раза буквы
a, b, c
(и
1234
один раз):
#!/bin/sh
./lastmsg -c 1234
(
for i in `seq 3`; do ./lastmsg a & ./lastmsg b & ./lastmsg c & done; wait
./lastmsg -d
)
Теперь начинается сама проблема. Когда я пытаюсь перенаправить stdout в файл, то «синхронизация» магическим образом пропадает, т.е.:
#!/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
Должно выводить в консоль: 3 3 3 (на новой строке каждый раз), но выводит иногда 2 3 2, иногда 2 3 3 — т.е. каждый раз разные значенея (иногда и 3 3 3 выводит). Мне кажется, что проблема в операторе ">", но на 100% быть не могу уверен. Поиск по гуглу ничего не дал.
Каковы ваши мысли на этот счет? Есть ли способ решить проблему?
P.S. для чего нужно выводить в файл, если при выводе в консоль всё работает? Если я захочу проверить синхронизацию 1000 параллельных процессов (`seq 1000`), то это единственный способ как это можно проверить (ну не считать же 3000 символов вручную?)
Заранее спасибо!
UPD:
Вот собственно сама программа:
Скрытый текст/**
* @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);
}
Или кто препдопичатет через пастебин:
pastebin.com/267DRUK0
Makefile (компилируется без предупреждений):
Скрытый текст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
Пояснения к коду:
Наиболее важными являются строки 201-224 — это то место, где и происходит синхронизация процессов, вывод в консоль на 207 строке, запись в память в 211.
fflush(stdout);
не помог, но вывод на stderr (вместо stdout) принес результаты — при `seq 3` синхронизируется отлично (3 3 3 каждый раз), но вот при `seq 1000` уже идет расхождение (970 980 970 примерно такие числа). Отсюда следует, что ошибка всё-таки в программе. Я правильно понял?
В программе для синхронизации используется
https://github.com/osue-tuwien/SE библиотека (её и надо использовать по заданию). Её придется дополнительно установить и потом через линкинг присоеденить к программе (поэтому в Makefille -l
seqev опция стоит).