介紹
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)