QNX® Neutrino 進程間通信編程之Shared Memory

介紹

Interprocess Communication(IPC,進程間通信)在QNX Neutrino從一個嵌入式實時系統向一個全面的POSIX系統轉變起着至關重要的作用。IPC是將在內核中提供各種服務的進程內聚在一起的粘合劑。在QNX中,消息傳遞是IPC的主要形式,也提供了其他的形式,除非有特殊的說明,否則這些形式也都是基於本地消息傳遞而實現的。

將更高級別的 IPC 服務(如通過我們的消息傳遞實現的管道和 FIFO)與其宏內核對應物進行比較的基準測試表明性能相當。

QNX Neutrino提供以下形式的IPC:

Service: Implemented in:
Message-passing Kernel
Pules Kernel
Signals Kernel
Event Delivery External process
POSIX message queues External process
Shared memory Process manager
Pipes External process
FIFOs External process

本篇幅介紹的是POSIX IPC Shared Memory。

Shared memory

共享內存提供了最高帶寬的IPC機制,一旦創建了共享內存對象,訪問對象的進程可以使用指針直接對其進行讀寫操作。共享內存本身是不同步的,需要結合同步原語一起使用,信號量和互斥鎖都適合與共享內存一塊使用,信號量一般用於進程之間的同步,而互斥鎖通常用於線程之間的同步,通通常來說互斥鎖的效率會比信號量要高。

共享內存與消息傳遞結合起來的IPC機制,可以提供以下特點:

  • 非常高的性能(共享內存)
  • 同步(消息傳遞)
  • 跨網絡傳遞(消息傳遞)

QNX中消息傳遞通過拷貝完成,當消息較大時,可以通過共享內存來完成,發送消息時不需要發送整個消息內容,只需將消息保存到共享內存中,並將地址傳遞過去即可。通常會使用mmap來將共享內存區域映射到進程地址空間中來,如下圖所示:

進程中的線程之間自動共享內存。通過設置shared memory,同樣的物理內存可以被多個進程訪問。

共享內存建立流程:

fd = shm_open( “/myname”, O_RDWR|O_CREAT, 0666 );
// 共享內存的名字必須是唯一的。
ftruncate( fd, SHARED_SIZE ); 
// 通過ftruncate分配共享內存對象大小,這個SHARED_SIZE將四捨五入爲頁面(4KB)大小的倍數。
ptr = mmap( NULL, SHARED_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );
//mmap 將對應的物理地址空間映射出相應的虛擬地址空間,然後下一步並將其初始化。
close(fd); 
//你不在使用文件句柄的時候,你需要將它關閉

共享內存訪問流程:

fd = shm_open( “/myname”,O_RDWR );
//使用共享內存是,共享內存名必須一樣。
ptr = mmap( NULL, SHARED_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );
//如果對共享內存只讀訪問,那麼不必使用PROT_WRITE屬性。
//如果對共享內存模塊子塊進行訪問,只需要將最後一個參數offset零替換成你對應子塊的偏移,大小也需要做相應的調整。
close(fd); 
//你不在使用文件句柄的時候,你需要將它關閉

清除共享內存流程:

// 每個句柄,mapping,共享內存名都是一個參考依據
// 可以明確關閉和取消映射
close(fd);
munmap( ptr, SHARED_SIZE );
// 在進程死亡時,所有 fds 都會自動關閉並且所有映射都未映射
// 必須明確刪除名稱:
shm_unlink( “/myname” );
// 在開發和測試期間,這可以從命令行手動完成:
rm /dev/shmem/myname

共享內存的問題:

– readers不知道數據何時穩定 – writers不知道什麼時候寫作是安全的

所以這樣涉及到了同步的操作。

  • 共享內存區域中的線程同步對象
  • 如果使用 sem_init(),則 pshared 參數必須非零
  • 互斥體和條件變量需要 PTHREAD_PROCESS_SHARED 標誌屬性
  • 使用atomic_*() 函數來操作變量
  • IPC
  • MsgSend()/MsgReceive()/MsgReply() 具有內置同步功能
  • 使用共享內存避免大數據拷貝

Process進程間通過shared memory 通信同步策略:

POSIX共享內存API

Function Description Classification
shm_open() Open (or create) a shared memory region. POSIX
close() Close a shared memory region. POSIX
mmap() Map a shared memory region into a process's address space. POSIX
munmap() Unmap a shared memory region from a process's address space. POSIX
munmap_flags() Unmap previously mapped addresses, exercising more control than possible with munmap() QNX Neutrino
mprotect() Change protections on a shared memory region. POSIX
msync() Synchronize memory with physical storage. POSIX
shm_ctl(), shm_ctl_special() Give special attributes to a shared memory object. QNX Neutrino
shm_unlink() Remove a shared memory region. POSIX

函數shm_open和shm_unlink非常類似於爲普通文件所提供的open和unlink系統調用。如果要編寫一個可移植的程序,那麼shm_open和shm_unlink是最好的選擇。

  • shm_open:創建一個新的共享區域或者附加在已有的共享區域上.區域被其名字標識,函數返回各文件的描述符。
#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */
int shm_open(const char *name, int oflag, mode_t mode);

參數:

name: 共享內存名字;

oflag: 與open函數類型, 可以是O_RDONLY, O_WRONLY, O_RDWR, 還可以按位或上O_CREAT, O_EXCL, O_TRUNC.

mode: 此參數總是需要設置, 如果oflag沒有指定O_CREAT, 則mode可以設置爲0;

返回值:

成功: 返回一個文件描述符;

失敗: 返回-1;

  • ftruncate:修改共享內存大小。
int ftruncate(int fd, off_t length);

參數:

fd:文件描述符

length:長度

返回值:

成功返回0,失敗返回-1

  • shm_unlink:類似於unlink系統調用對文件進行操作,直到所有的進程不再引用該內存區後纔對其進行釋放。
int shm_unlink(const char *name);

參數:

name:共享內存對象的名字

返回值:

成功返回0,失敗返回-1

  • mmap:用於將一個文件映射到某一內存區中,其中也使用了shm_open函數返回的文件描述符。
  • munmap:用於釋放mmap所映射的內存區域。
#include <sys/mman.h>
void * mmap( void *where_i_want_it,
             size_t length,
             int memory_protections,
             int mapping_flags,
             int fd,
             off_t offset_within_shared_memory );
int munmap(void *addr, size_t length);

參數:

where_i_want_it: 要映射的起始地址, 通常指定爲NULL, 讓內核自動選擇;

length: 映射到進程地址空間的字節數, 通常是先前已經創建的共享內存的大小;

memory_protections: 映射區保護方式(見下);

mmap()的返回值將是進程映射對象的地址空間中的地址。參數 where_i_want_it 用作系統提示您放置對象的位置。如果可能,該對象將被放置在所請求的地址。大多數應用程序指定的地址爲零,這使系統可以自由地將對象放置在所需的位置。

可以爲 memory_protections 指定以下保護類型:

  • PROT_EXEC表示映射的內存頁可執行
  • PROT_READ表示映射的內存可被讀
  • PROT_WRITE表示映射的內存可被寫
  • PROT_NONE表示映射的內存不可訪問

當您使用共享內存區域訪問可由硬件修改的雙端口內存(例如,視頻幀緩衝區或內存映射網絡或通信板)時,應使用 PROT_NOCACHE 清單。如果沒有此清單,處理器可能會從先前緩存的讀取中返回“陳舊”數據。

mapping_flags: 標誌(通常指定爲MAP_SHARED, 用於進程間通信);

這些標誌分爲兩部分-第一部分是類型,必須指定爲以下之一:

  • MAP_SHARED表示共享這塊映射的內存,讀寫這塊內存相當於直接讀寫文件,這些操作對其他進程可見,由於OS對文件的讀寫都有緩存機制,所以實際上不會立即將更改寫入文件,除非帶哦用msync()或mumap()
  • MAP_PRIVATE表示創建一個私有的copy-on-write的映射, 更新映射區對其他映射到這個文件的進程是不可見的

fd: 文件描述符(填爲shm_open返回的共享內存ID);

offset_within_shared_memory: 從文件頭開始的偏移量(一般填爲0);

mmap返回值:

成功: 返回映射到的內存區的起始地址;

失敗: 返回-1;

  • msync:同步存取一個映射區域並將高速緩存的數據回寫到物理內存中,以便其他進程可以監聽這些改變。
#include <sys/mman.h>
int msync(void *start, size_t length, int flags);

flags值可爲 MS_ASYNC,MS_SYNC,MS_INVALIDATE

  • MS_ASYNC的作用是,不管映射區是否更新,直接沖洗返回。
  • MS_SYNC的作用是,如果映射區更新了,則沖洗返回,如果映射區沒有更新,則等待,知道更新完畢,就沖洗返回。
  • MS_INVALIDATE的作用是,丟棄映射區中和原文件相同的部分。

具體實例如下:

客戶端進程代碼如下:

/*
 *
 *       client.c: Write strings for printing in POSIX shared memory object
 *                 
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/mman.h>

// Buffer data structures
#define MAX_BUFFERS 10

#define LOGFILE "/tmp/example.log"

#define SEM_MUTEX_NAME "/sem-mutex"
#define SEM_BUFFER_COUNT_NAME "/sem-buffer-count"
#define SEM_SPOOL_SIGNAL_NAME "/sem-spool-signal"
#define SHARED_MEM_NAME "/posix-shared-mem-example"

struct shared_memory {
    char buf [MAX_BUFFERS] [256];
    int buffer_index;
    int buffer_print_index;
};

void error (char *msg);

int main (int argc, char **argv)
{
    struct shared_memory *shared_mem_ptr;
    sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;
    int fd_shm;
    char mybuf [256];
    
    //  mutual exclusion semaphore, mutex_sem 
    if ((mutex_sem = sem_open (SEM_MUTEX_NAME, 0, 0, 0)) == SEM_FAILED)
        error ("sem_open");
    
    // Get shared memory 
    if ((fd_shm = shm_open (SHARED_MEM_NAME, O_RDWR, 0)) == -1)
        error ("shm_open");

    if ((shared_mem_ptr = mmap (NULL, sizeof (struct shared_memory), PROT_READ | PROT_WRITE, MAP_SHARED,
            fd_shm, 0)) == MAP_FAILED)
       error ("mmap");

    // counting semaphore, indicating the number of available buffers.
    if ((buffer_count_sem = sem_open (SEM_BUFFER_COUNT_NAME, 0, 0, 0)) == SEM_FAILED)
        error ("sem_open");

    // counting semaphore, indicating the number of strings to be printed. Initial value = 0
    if ((spool_signal_sem = sem_open (SEM_SPOOL_SIGNAL_NAME, 0, 0, 0)) == SEM_FAILED)
        error ("sem_open");

    char buf [200], *cp;

    printf ("Please type a message: ");

    while (fgets (buf, 198, stdin)) {
        // remove newline from string
        int length = strlen (buf);
        if (buf [length - 1] == '\n')
           buf [length - 1] = '\0';

        // get a buffer: P (buffer_count_sem);
        if (sem_wait (buffer_count_sem) == -1)
            error ("sem_wait: buffer_count_sem");
    
        /* There might be multiple producers. We must ensure that 
            only one producer uses buffer_index at a time.  */
        // P (mutex_sem);
        if (sem_wait (mutex_sem) == -1)
            error ("sem_wait: mutex_sem");

	    // Critical section
            time_t now = time (NULL);
            cp = ctime (&now);
            int len = strlen (cp);
            if (*(cp + len -1) == '\n')
                *(cp + len -1) = '\0';
            sprintf (shared_mem_ptr -> buf [shared_mem_ptr -> buffer_index], "%d: %s %s\n", getpid (), 
                     cp, buf);
            (shared_mem_ptr -> buffer_index)++;
            if (shared_mem_ptr -> buffer_index == MAX_BUFFERS)
                shared_mem_ptr -> buffer_index = 0;

        // Release mutex sem: V (mutex_sem)
        if (sem_post (mutex_sem) == -1)
            error ("sem_post: mutex_sem");
    
	// Tell spooler that there is a string to print: V (spool_signal_sem);
        if (sem_post (spool_signal_sem) == -1)
            error ("sem_post: (spool_signal_sem");

        printf ("Please type a message: ");
    }
 
    if (munmap (shared_mem_ptr, sizeof (struct shared_memory)) == -1)
        error ("munmap");
    exit (0);
}

// Print system error and exit
void error (char *msg)
{
    perror (msg);
    exit (1);
}

服務器端進程代碼如下:

/*
 *
 *       logger.c: Write strings in POSIX shared memory to file
 *                 (Server process)
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/mman.h>

// Buffer data structures
#define MAX_BUFFERS 10

#define LOGFILE "/tmp/example.log"

#define SEM_MUTEX_NAME "/sem-mutex"
#define SEM_BUFFER_COUNT_NAME "/sem-buffer-count"
#define SEM_SPOOL_SIGNAL_NAME "/sem-spool-signal"
#define SHARED_MEM_NAME "/posix-shared-mem-example"

struct shared_memory {
    char buf [MAX_BUFFERS] [256];
    int buffer_index;
    int buffer_print_index;
};

void error (char *msg);

int main (int argc, char **argv)
{
    struct shared_memory *shared_mem_ptr;
    sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;
    int fd_shm, fd_log;
    char mybuf [256];
    
    // Open log file
    if ((fd_log = open (LOGFILE, O_CREAT | O_WRONLY | O_APPEND | O_SYNC, 0666)) == -1)
        error ("fopen");

    //  mutual exclusion semaphore, mutex_sem with an initial value 0.
    if ((mutex_sem = sem_open (SEM_MUTEX_NAME, O_CREAT, 0660, 0)) == SEM_FAILED)
        error ("sem_open");
    
    // Get shared memory 
    if ((fd_shm = shm_open (SHARED_MEM_NAME, O_RDWR | O_CREAT, 0660)) == -1)
        error ("shm_open");

    if (ftruncate (fd_shm, sizeof (struct shared_memory)) == -1)
       error ("ftruncate");
    
    if ((shared_mem_ptr = mmap (NULL, sizeof (struct shared_memory), PROT_READ | PROT_WRITE, MAP_SHARED,
            fd_shm, 0)) == MAP_FAILED)
       error ("mmap");
    // Initialize the shared memory
    shared_mem_ptr -> buffer_index = shared_mem_ptr -> buffer_print_index = 0;

    // counting semaphore, indicating the number of available buffers. Initial value = MAX_BUFFERS
    if ((buffer_count_sem = sem_open (SEM_BUFFER_COUNT_NAME, O_CREAT, 0660, MAX_BUFFERS)) == SEM_FAILED)
        error ("sem_open");

    // counting semaphore, indicating the number of strings to be printed. Initial value = 0
    if ((spool_signal_sem = sem_open (SEM_SPOOL_SIGNAL_NAME, O_CREAT, 0660, 0)) == SEM_FAILED)
        error ("sem_open");

    // Initialization complete; now we can set mutex semaphore as 1 to 
    // indicate shared memory segment is available
    if (sem_post (mutex_sem) == -1)
        error ("sem_post: mutex_sem");
    
    while (1) {  // forever
        // Is there a string to print? P (spool_signal_sem);
        if (sem_wait (spool_signal_sem) == -1)
            error ("sem_wait: spool_signal_sem");
    
        strcpy (mybuf, shared_mem_ptr -> buf [shared_mem_ptr -> buffer_print_index]);

        /* Since there is only one process (the logger) using the 
           buffer_print_index, mutex semaphore is not necessary */
        (shared_mem_ptr -> buffer_print_index)++;
        if (shared_mem_ptr -> buffer_print_index == MAX_BUFFERS)
           shared_mem_ptr -> buffer_print_index = 0;

        /* Contents of one buffer has been printed.
           One more buffer is available for use by producers.
           Release buffer: V (buffer_count_sem);  */
        if (sem_post (buffer_count_sem) == -1)
            error ("sem_post: buffer_count_sem");
        
        // write the string to file
        if (write (fd_log, mybuf, strlen (mybuf)) != strlen (mybuf))
            error ("write: logfile");
    }
}

// Print system error and exit
void error (char *msg)
{
    perror (msg);
    exit (1);
}

客戶端和服務器端編譯命令和輸出結果如下:

gcc -pthread client.c -lrt -o client
gcc -pthread server.c -lrt -o server
# 服務器進程加載
bspserver@ubuntu:~/workspace/posix_share_memory$ ./server

# 客戶進程 A 
bspserver@ubuntu:~/workspace/posix_share_memory$ ./client
Please type a message: Fuck U~ Client A

# 客戶進程 B 
bspserver@ubuntu:~/workspace/posix_share_memory$ ./client
Please type a message: I Love U! Client B

# 客戶進程 C
bspserver@ubuntu:~/workspace/posix_share_memory$ ./client
Please type a message: Hello World! Client C

#在/tmp/目錄下查詢example.log日誌
bspserver@ubuntu:~/workspace/posix_share_memory$cat /tmp/example.log
2785: Fri Dec 17 02:02:11 2021 Hello World! Client C
2695: Fri Dec 17 02:02:40 2021 I Love U! Client B
2788: Fri Dec 17 02:04:40 2021 Fuck U~ Client A

參考文獻:

Programming with POSIX Threads
System Architecture - Interprocess Communication (IPC)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章