[ 嵌入式通信技术 ] [ 3 ] 线程间通信

邮箱

1. 邮箱中的邮件排列是有顺序的,一般遵循 FIFO 规则。在正常使用中,往往在需要为邮箱通信的每一对线程中建立一个专用邮箱。

2. RTT 的邮箱规定,每个邮件的内容只能为 4 个字节。如果发送内容过多,可以把要发送的内容封装成一个结构体,然后发送结构体指针。当接收到该指针时,线程最好尽快处理或将内容复制出来(如何获取数据的大小?

3. 邮箱的创建分为静态和动态两种。静态对象的句柄(就是结构体地址)需要提前声明,这部分内存空间永远被占用,OS 无法释放静态对象的内存。然而,动态创建的邮箱可以被 OS 释放,正常情况下并不需要频繁地创建和释放邮箱(注意内存泄漏问题)。

消息队列

1. 消息队列是对邮箱的扩展,解决了邮箱发送邮件大小受限制的问题。

2. 类比线程池概念,创建消息队列时,OS 会自动分配消息队列所占用的空间。并维护两个链表:空闲链表和就绪链表。

3. “紧急消息” 可以把消息插入到消息队列最前端,从而使该消息提前被执行。

系统中,关于消息队列的操作:

NAME

       msgrcv, msgsnd - System V message queue operations

DESCRIPTION

       The  msgsnd()  and  msgrcv()  system calls are used, respectively, to send messages to, and receive messages from, a System V message queue.  The calling process must have write

       permission on the message queue in order to send a message, and read permission to receive a message.

       The msgp argument is a pointer to a caller-defined structure of the following general form:

           struct msgbuf {

               long mtype;       /* message type, must be > 0 */

               char mtext[1];    /* message data */

           };

       The mtext field is an array (or other structure) whose size is specified by msgsz, a nonnegative integer value.  Messages of zero length (i.e., no mtext  field)  are  permitted.

       The mtype field must have a strictly positive integer value.  This value can be used by the receiving process for message selection (see the description of msgrcv() below).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct msgbuf {
    long mtype;
    char mtext[80];
};

static void usage(char *prog_name, char *msg)
{
    if (msg != NULL)
        fputs(msg, stderr);

    fprintf(stderr, "Usage: %s [options]\n", prog_name);
    fprintf(stderr, "Options are:\n");
    fprintf(stderr, "-s        send message using msgsnd()\n");
    fprintf(stderr, "-r        read message using msgrcv()\n");
    fprintf(stderr, "-t        message type (default is 1)\n");
    fprintf(stderr, "-k        message queue key (default is 1234)\n");
    exit(EXIT_FAILURE);
}

static void send_msg(int qid, int msgtype)
{
    struct msgbuf msg;
    time_t t;

    msg.mtype = msgtype;

    time(&t);
    snprintf(msg.mtext, sizeof(msg.mtext), "a message at %s",
            ctime(&t));

    if (msgsnd(qid, (void *) &msg, sizeof(msg.mtext),
                IPC_NOWAIT) == -1) {
        perror("msgsnd error");
        exit(EXIT_FAILURE);
    }
    printf("sent: %s\n", msg.mtext);
}

static void get_msg(int qid, int msgtype)
{
    struct msgbuf msg;

    if (msgrcv(qid, (void *) &msg, sizeof(msg.mtext), msgtype,
               MSG_NOERROR | IPC_NOWAIT) == -1) {
        if (errno != ENOMSG) {
            perror("msgrcv");
            exit(EXIT_FAILURE);
        }
        printf("No message available for msgrcv()\n");
    } else
        printf("message received: %s\n", msg.mtext);
}

int main(int argc, char *argv[])
{
    int qid, opt;
    int mode = 0;               /* 1 = send, 2 = receive */
    int msgtype = 1;
    int msgkey = 1234;

    while ((opt = getopt(argc, argv, "srt:k:")) != -1) {
        switch (opt) {
        case 's':
            mode = 1;
            break;
        case 'r':
            mode = 2;
            break;
        case 't':
            msgtype = atoi(optarg);
            if (msgtype <= 0)
                usage(argv[0], "-t option must be greater than 0\n");
            break;
        case 'k':
            msgkey = atoi(optarg);
            break;
        default:
            usage(argv[0], "Unrecognized option\n");
        }
    }

    if (mode == 0)
        usage(argv[0], "must use either -s or -r option\n");

    qid = msgget(msgkey, IPC_CREAT | 0666);

    if (qid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    if (mode == 2)
        get_msg(qid, msgtype);
    else
        send_msg(qid, msgtype);

    exit(EXIT_SUCCESS);
}

信号

1. 这里的信号和前面线程同步中的信号量(semaphore)以及消息机制都不同,它模拟了一个硬件上的中断,通常的实现方法是使用芯片提供的软中断指令,所以它本质上也是一个中断。

2. 信号处理函数类似于中断处理函数,该函数会在信号被触发后自动调用(回调函数)。

3. 信号是一个软中断,不存在等待与否的问题。

共享内存

共享内存就是两个不相关的进程之间可以直接访问同一段内存,共享内存在两个正在运行的进程之间共享和传递数据。

本质上是映射一段可以被其他进程所访问到的内存,这段内存有一个进程创建,但是多个进程都可以访问。

共享内存函数由 shmget、shmat、shmdt 和 shmctl 四个函数组成。

shm_snd.c

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_SIZE 1024

int main() {
    int shmid;
    char* shmptr;
    // 创建共享内存
    shmid = shmget(0x66, SHM_SIZE, IPC_CREAT|0666);
    if (shmid < 0) {
        perror("shmget");
        return -1;
    }
    // 对共享内存的访问
    shmptr = shmat(shmid, 0, 0);
    if (shmptr == (void*)-1) {
        perror("shmat");
        return -2;
    }
    // 往共享内存写数据
    strcpy(shmptr, "shmat write ok");
    shmdt(shmptr);
    return 0;
}

shm_rcv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_SIZE 1024

int main() {
    int shmid;
    char* shmptr;
    shmid = shmget(0x66, SHM_SIZE, IPC_CREAT|0666);
    if (shmid < 0) {
        perror("shmget");
        return -1;
    }
    shmptr = shmat(shmid, 0, 0);
    if (shmptr == (void*)-1) {
        perror("shmat");
        return -2;
    }

    // read from shared memory
    printf("read: %s\n", shmptr);
    shmdt(shmptr);

    return 0;
}