linux-IPC:信号量/共享内存/消息队列

原创:https://blog.csdn.net/ndzjx/article/details/89018951

进程间通信的机制,三种:

1:信号量(不同于线程POSIX信号量,这里是进程间)

2:共享内存

3:消息队列

信号量:二进制信号量/通用信号量

#include <sys/sem.h>

int semctl(int sem_id, int sem_num, int command, …)

int semget(key_t key, int num_sems, int sem_flags);

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

这些函数都是被设计用来对成组的信号量值来操作的。

key很像一个文件名,semget返回的与fopen返回的FILE*文件流很类似,不同进程可以用不同的信号量标识符指向同一个信号量。

semget: key不相关的进程可以通过它访问同一个信号量。其他信号量函数都使用semget返回的信号量标识。

num_sems,指定需要的信号量数目,几乎总是取1

sem_flags,一组标志,低端的9个比特是该信号量的权限,可以用IPC_CREATE 与权限按位或来取值。

semop: sembuf结构:

struct sembuf {

short sem_num; 信号量编号,除非使用一组信号量,否则,取值一般为0

short sem_op; 一次操作需要改变的数值,通常为1(P)或-1(V)

short sem_flg; 通常为SEM_UNDO,系统跟踪修改情况,进程没释放就终止时,系统自动释放它。

}

semctl: 如果有第四个参数,会是union semun结构:

union semun{

int val;

struct semid_ds *buf;

unsigned short *array;

}

command 要采取的动作:通常是两个值:

SETVAL:把信号量初始化为一个已知的值。值在union semun中的val中。

IPC_RMID:删除无需继续使用的信号量标识符。

semun.h

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
    /* union semun is defined by including <sys/sem.h> */
#else
    /* according to X/OPEN we have to define it ourselves */
    union semun {
        int val;                    /* value for SETVAL */
        struct semid_ds *buf;       /* buffer for IPC_STAT, IPC_SET */
        unsigned short int *array;  /* array for GETALL, SETALL */
        struct seminfo *__buf;      /* buffer for IPC_INFO */
    };
#endif

sem1.c

/* After the #includes, the function prototypes and the global variable, we come to the
 main function. There the semaphore is created with a call to semget, which returns the
 semaphore ID. If the program is the first to be called (i.e. it's called with a parameter
 and argc > 1), a call is made to set_semvalue to initialize the semaphore and op_char is
 set to X. */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#include "semun.h"

static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);

static int sem_id;


int main(int argc, char *argv[])
{
    int i;
    int pause_time;
    char op_char = 'O';

    srand((unsigned int)getpid());
    
    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);

    if (argc > 1) {
        if (!set_semvalue()) {
            fprintf(stderr, "Failed to initialize semaphore\n");
            exit(EXIT_FAILURE);
        }
        op_char = 'X';
        sleep(2);
    }

/* Then we have a loop which enters and leaves the critical section ten times.
 There, we first make a call to semaphore_p which sets the semaphore to wait, as
 this program is about to enter the critical section. */

    for(i = 0; i < 10; i++) {        

        if (!semaphore_p()) exit(EXIT_FAILURE);
        printf("%c", op_char);fflush(stdout);
        pause_time = rand() % 3;
        sleep(pause_time);
        printf("%c", op_char);fflush(stdout);

/* After the critical section, we call semaphore_v, setting the semaphore available,
 before going through the for loop again after a random wait. After the loop, the call
 to del_semvalue is made to clean up the code. */

        if (!semaphore_v()) exit(EXIT_FAILURE);
        
        pause_time = rand() % 2;
        sleep(pause_time);
    }    

    printf("\n%d - finished\n", getpid());

    if (argc > 1) {    
        sleep(10);
        del_semvalue();
    }
        
    exit(EXIT_SUCCESS);
}

/* The function set_semvalue initializes the semaphore using the SETVAL command in a
 semctl call. We need to do this before we can use the semaphore. */

static int set_semvalue(void)
{
    union semun sem_union;

    sem_union.val = 1;
    if (semctl(sem_id, 0, SETVAL, sem_union) == -1) return(0);
    return(1);
}

/* The del_semvalue function has almost the same form, except the call to semctl uses
 the command IPC_RMID to remove the semaphore's ID. */

static void del_semvalue(void)
{
    union semun sem_union;
    
    if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
        fprintf(stderr, "Failed to delete semaphore\n");
}

/* semaphore_p changes the semaphore by -1 (waiting). */

static int semaphore_p(void)
{
    struct sembuf sem_b;
    
    sem_b.sem_num = 0;
    sem_b.sem_op = -1; /* P() */
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1) {
        fprintf(stderr, "semaphore_p failed\n");
        return(0);
    }
    return(1);
}

/* semaphore_v is similar except for setting the sem_op part of the sembuf structure to 1,
 so that the semaphore becomes available. */

static int semaphore_v(void)
{
    struct sembuf sem_b;
    
    sem_b.sem_num = 0;
    sem_b.sem_op = 1; /* V() */
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1) {
        fprintf(stderr, "semaphore_v failed\n");
        return(0);
    }
    return(1);
}

$ ./sem1 1 &

$ ./sem1

共享内存:

并未提供同步机制。

#include <sys/shm.h>

void *shmat(int shm_id, const void *shm_addr, int shmflg);//将共享内存连接到地址空间中,SHM_RDONLY标志(使得连接的内存只读)

int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

int shmdt(const void *shm_addr);//将共享内存分离,并未删除它。

int shmget(key_t key, size_t size, int shmflg); //创建

shmctl中shmid_ds结构:

struct shmid_ds {

uid_t shm_perm.uid;

uid_t shm_perm.gid;

mode_t shm_perm.mode;

}

command可以取三个值:

IPC_STAT/IPC_SET/IPC_RMID(删除共享内存段)

shm_com.h

/* A common header file to describe the shared memory we wish to pass about. */

#define TEXT_SZ 2048

struct shared_use_st {
    int written_by_you;
    char some_text[TEXT_SZ];
};

shm1.c

/* Our first program is a consumer. After the headers the shared memory segment
 (the size of our shared memory structure) is created with a call to shmget,
 with the IPC_CREAT bit specified. */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include "shm_com.h"

int main()
{
    int running = 1;
    void *shared_memory = (void *)0;
    struct shared_use_st *shared_stuff;
    int shmid;

    srand((unsigned int)getpid());    

    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);

    if (shmid == -1) {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }

/* We now make the shared memory accessible to the program. */

    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1) {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }

    printf("Memory attached at %X\n", (int)shared_memory);

/* The next portion of the program assigns the shared_memory segment to shared_stuff,
 which then prints out any text in written_by_you. The loop continues until end is found
 in written_by_you. The call to sleep forces the consumer to sit in its critical section,
 which makes the producer wait. */

    shared_stuff = (struct shared_use_st *)shared_memory;
    shared_stuff->written_by_you = 0;
    while(running) {
        if (shared_stuff->written_by_you) {
            printf("You wrote: %s", shared_stuff->some_text);
            sleep( rand() % 4 ); /* make the other process wait for us ! */
            shared_stuff->written_by_you = 0;
            if (strncmp(shared_stuff->some_text, "end", 3) == 0) {
                running = 0;
            }
        }
    }

/* Lastly, the shared memory is detached and then deleted. */

    if (shmdt(shared_memory) == -1) {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }

    if (shmctl(shmid, IPC_RMID, 0) == -1) {
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

shm2.c

/* The second program is the producer and allows us to enter data for consumers.
 It's very similar to shm1.c and looks like this. */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include "shm_com.h"

int main()
{
    int running = 1;
    void *shared_memory = (void *)0;
    struct shared_use_st *shared_stuff;
    char buffer[BUFSIZ];
    int shmid;

    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);

    if (shmid == -1) {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }

    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1) {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }

    printf("Memory attached at %X\n", (int)shared_memory);

    shared_stuff = (struct shared_use_st *)shared_memory;
    while(running) {
        while(shared_stuff->written_by_you == 1) {
            sleep(1);            
            printf("waiting for client...\n");
        }
        printf("Enter some text: ");
        fgets(buffer, BUFSIZ, stdin);
        
        strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
        shared_stuff->written_by_you = 1;

        if (strncmp(buffer, "end", 3) == 0) {
                running = 0;
        }
    }

    if (shmdt(shared_memory) == -1) {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

消息队列:

优势:

1:独立于发送和接收进程而存在。

2:可以提前查看紧急消息

但,与管道一样,每个数据块都有一个最大长度限制,系统中所有队列所包含的全部数据块的总长度也有一个上限。

MSGMAX,消息的最大长度

MSGMNB,队列的最大长度

#include <sys/msg.h>

int msgctl(int msgid, int cmd, struct msqid_ds *buf)

int msgget(key_t key, int msgflg) //创建

int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);

int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

消息的结构受两方面约束:

1:长度必须小于系统规定的上限

2:必须以一个长整型成员变量开始

stuct my_message{

long int message_type;

/* The date you wish to transfer */

}

msgrcv/msgsnd中msg_sz是msg_ptr指向的消息的长度,这个长度不能包括长整型消息类型成员变量的长度。

msgflg控制消息队列满或队列消息达到系统的限制时要发生的事情,IPC_NOWAIT进程挂起以等待队列腾出可用空间。

msgrcv中msgtype代表接收优先级,为0,获取第一个可用消息。大于0,获取具有相同消息类型的第一个消息。小于0,获取消息类型<=msgtype的绝对值的第一个消息。

例如,如果想接收类型<=n的消息,就把msgtype设置为-n

成功时,返回放到接收缓存区中的字节数。

msg1.c

/* Here's the receiver program. */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>


struct my_msg_st {
    long int my_msg_type;
    char some_text[BUFSIZ];
};

int main()
{
    int running = 1;
    int msgid;
    struct my_msg_st some_data;
    long int msg_to_receive = 0;

/* First, we set up the message queue. */

    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);

    if (msgid == -1) {
        fprintf(stderr, "msgget failed with error: %d\n", errno);
        exit(EXIT_FAILURE);
    }

/* Then the messages are retrieved from the queue, until an end message is encountered.
 Lastly, the message queue is deleted. */

    while(running) {
        if (msgrcv(msgid, (void *)&some_data, BUFSIZ,
                   msg_to_receive, 0) == -1) {
            fprintf(stderr, "msgrcv failed with error: %d\n", errno);
            exit(EXIT_FAILURE);
        }
        printf("You wrote: %s", some_data.some_text);
        if (strncmp(some_data.some_text, "end", 3) == 0) {
            running = 0;
        }
    }

    if (msgctl(msgid, IPC_RMID, 0) == -1) {
        fprintf(stderr, "msgctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

msg2.c

/* The sender program is very similar to msg1.c. In the main set up, delete the
 msg_to_receive declaration and replace it with buffer[BUFSIZ], remove the message
 queue delete and make the following changes to the running loop.
 We now have a call to msgsnd to send the entered text to the queue. */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define MAX_TEXT 512

struct my_msg_st {
    long int my_msg_type;
    char some_text[MAX_TEXT];
};

int main()
{
    int running = 1;
    struct my_msg_st some_data;
    int msgid;
    char buffer[BUFSIZ];

    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);

    if (msgid == -1) {
        fprintf(stderr, "msgget failed with error: %d\n", errno);
        exit(EXIT_FAILURE);
    }

    while(running) {
        printf("Enter some text: ");
        fgets(buffer, BUFSIZ, stdin);
        some_data.my_msg_type = 1;
        strcpy(some_data.some_text, buffer);

        if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1) {
            fprintf(stderr, "msgsnd failed\n");
            exit(EXIT_FAILURE);
        }
        if (strncmp(buffer, "end", 3) == 0) {
            running = 0;
        }
    }

    exit(EXIT_SUCCESS);
}

假设消息队列中有空间,可以先发送,然后在接收者启动之前就退出:

./msg2

./msg1

IPC机制一个让人烦恼的问题:编写错误的程序,或因为某些原因而执行失败的程序将把它的IPC资源(如消息队列中的数据)遗留在系统中。

一些命令可以查看和删除IPC

./ipcs -s 查看信号量

./ipcs -m 查看共享内存

./ipcs -q 茶康消息队列

./ipcrm -s 768 或 ./ipcrm sem 768 删除指定信号量

./ipcrm -m <id> 删除共享内存

./ipcrm -q <id> 删除消息队列

猜你喜欢

转载自blog.csdn.net/ndzjx/article/details/89018951