今天我們來(lái)講講進(jìn)程間使用共享內(nèi)存通信時(shí)為了確保數(shù)據(jù)的正確,如何進(jìn)行同步?
在Linux中,進(jìn)程間的共享內(nèi)存通信需要通過(guò)同步機(jī)制來(lái)保證數(shù)據(jù)的正確性和一致性,常用的同步機(jī)制包括信號(hào)量
、互斥鎖
、條件變量
等。
其中,使用信號(hào)量來(lái)同步進(jìn)程間的共享內(nèi)存訪問(wèn)是一種常見(jiàn)的方法。每個(gè)共享內(nèi)存區(qū)域可以關(guān)聯(lián)一個(gè)或多個(gè)信號(hào)量,以保護(hù)共享內(nèi)存區(qū)域的讀寫(xiě)操作。在訪問(wèn)共享內(nèi)存之前,進(jìn)程需要獲取信號(hào)量的使用權(quán),當(dāng)完成讀寫(xiě)操作后,再釋放信號(hào)量的使用權(quán),以便其他進(jìn)程可以訪問(wèn)共享內(nèi)存區(qū)域。
1、信號(hào)量同步
下面是一個(gè)簡(jiǎn)單的示例程序,展示了如何使用信號(hào)量來(lái)同步共享內(nèi)存區(qū)域的讀寫(xiě)操作:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#define SHM_SIZE 1024
#define SEM_KEY 0x123456
// 定義聯(lián)合體,用于信號(hào)量操作
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
int shmid, semid;
char *shmaddr;
struct sembuf semops[2];
union semun semarg;
// 創(chuàng)建共享內(nèi)存區(qū)域
shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 將共享內(nèi)存區(qū)域附加到進(jìn)程地址空間中
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (char *) -1) {
perror("shmat");
exit(1);
}
// 創(chuàng)建信號(hào)量
semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
// 初始化信號(hào)量值為1
semarg.val = 1;
if (semctl(semid, 0, SETVAL, semarg) == -1) {
perror("semctl");
exit(1);
}
// 等待信號(hào)量
semops[0].sem_num = 0;
semops[0].sem_op = 0;
semops[0].sem_flg = 0;
if (semop(semid, semops, 1) == -1) {
perror("semop");
exit(1);
}
// 在共享內(nèi)存中寫(xiě)入數(shù)據(jù)
strncpy(shmaddr, "Hello, world!", SHM_SIZE);
// 釋放信號(hào)量
semops[0].sem_num = 0;
semops[0].sem_op = 1;
semops[0].sem_flg = 0;
if (semop(semid, semops, 1) == -1) {
perror("semop");
exit(1);
}
// 等待信號(hào)量
semops[0].sem_num = 0;
semops[0].sem_op = 0;
semops[0].sem_flg = 0;
if (semop(semid, semops, 1) == -1) {
perror("semop");
exit(1);
}
// 從共享內(nèi)存中讀取數(shù)據(jù)
printf("Received message: %s\n", shmaddr);
// 釋放共享內(nèi)存區(qū)域
if (shmdt(shmaddr) == -1) {
perror("shmdt");
exit(1);
}
// 刪除共享內(nèi)存區(qū)域
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl");
exit(1);
}
// 刪除信號(hào)量
if (semctl(semid, 0, IPC_RMID, semarg) == -1) {
perror("semctl");
exit(1);
}
return 0;
在這個(gè)示例程序中,使用了System V信號(hào)量來(lái)同步共享內(nèi)存的讀寫(xiě)操作。程序首先創(chuàng)建一個(gè)共享內(nèi)存區(qū)域,并將其附加到進(jìn)程地址空間中。然后,使用semget()函數(shù)創(chuàng)建一個(gè)信號(hào)量,并將其初始化為1。在寫(xiě)入共享內(nèi)存數(shù)據(jù)之前,程序使用semop()函數(shù)等待信號(hào)量。一旦獲取了信號(hào)量的使用權(quán),程序就可以在共享內(nèi)存區(qū)域中寫(xiě)入數(shù)據(jù)。寫(xiě)入數(shù)據(jù)完成后,程序再次使用semop()函數(shù)釋放信號(hào)量的使用權(quán)。在讀取共享內(nèi)存數(shù)據(jù)時(shí),程序同樣需要等待信號(hào)量的使用權(quán),讀取數(shù)據(jù)完成后,再次釋放信號(hào)量的使用權(quán)。
需要注意的是,使用信號(hào)量來(lái)同步共享內(nèi)存訪問(wèn)時(shí),需要確保每個(gè)進(jìn)程都按照一定的順序進(jìn)行讀寫(xiě)操作。否則,就可能出現(xiàn)死鎖等問(wèn)題。因此,在設(shè)計(jì)進(jìn)程間共享內(nèi)存通信時(shí),需要仔細(xì)考慮數(shù)據(jù)的讀寫(xiě)順序,并采取合適的同步機(jī)制來(lái)確保數(shù)據(jù)的正確性和一致性。
2、互斥鎖同步
互斥量也是一種常用的同步機(jī)制,可以用來(lái)實(shí)現(xiàn)多個(gè)進(jìn)程之間的共享內(nèi)存訪問(wèn)。在Linux中,可以使用pthread庫(kù)中的互斥量來(lái)實(shí)現(xiàn)進(jìn)程間共享內(nèi)存的同步。
下面是一個(gè)使用互斥量實(shí)現(xiàn)共享內(nèi)存同步的示例程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHM_SIZE 1024
// 共享內(nèi)存結(jié)構(gòu)體
typedef struct {
pthread_mutex_t mutex;
char data[SHM_SIZE];
} shm_data_t;
int main() {
int fd;
shm_data_t *shm_data;
pthread_mutexattr_t mutex_attr;
pthread_mutex_t *mutex;
// 打開(kāi)共享內(nèi)存文件
if ((fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666)) == -1) {
perror("shm_open");
exit(1);
}
// 調(diào)整共享內(nèi)存文件大小
if (ftruncate(fd, sizeof(shm_data_t)) == -1) {
perror("ftruncate");
exit(1);
}
// 將共享內(nèi)存映射到進(jìn)程地址空間中
if ((shm_data = mmap(NULL, sizeof(shm_data_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
perror("mmap");
exit(1);
}
// 初始化互斥量屬性
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
// 創(chuàng)建互斥量
mutex = &(shm_data->mutex);
pthread_mutex_init(mutex, &mutex_attr);
// 在共享內(nèi)存中寫(xiě)入數(shù)據(jù)
pthread_mutex_lock(mutex);
sprintf(shm_data->data, "Hello, world!");
pthread_mutex_unlock(mutex);
// 在共享內(nèi)存中讀取數(shù)據(jù)
pthread_mutex_lock(mutex);
printf("Received message: %s\n", shm_data->data);
pthread_mutex_unlock(mutex);
// 解除共享內(nèi)存映射
if (munmap(shm_data, sizeof(shm_data_t)) == -1) {
perror("munmap");
exit(1);
}
// 刪除共享內(nèi)存文件
if (shm_unlink("/my_shm") == -1) {
perror("shm_unlink");
exit(1);
}
return 0;
}
在這個(gè)示例程序中,使用了pthread庫(kù)中的互斥量來(lái)同步共享內(nèi)存的讀寫(xiě)操作。程序首先創(chuàng)建一個(gè)共享內(nèi)存文件,并將其映射到進(jìn)程地址空間中。然后,使用pthread_mutex_init()函數(shù)創(chuàng)建一個(gè)互斥量,并將其初始化為共享內(nèi)存中的一部分。在寫(xiě)入共享內(nèi)存數(shù)據(jù)之前,程序使用pthread_mutex_lock()函數(shù)等待互斥量。一旦獲取了互斥量的使用權(quán),程序就可以在共享內(nèi)存區(qū)域中寫(xiě)入數(shù)據(jù)。寫(xiě)入數(shù)據(jù)完成后,程序再次使用pthread_mutex_unlock()函數(shù)釋放互斥量的使用權(quán)。在讀取共享內(nèi)存數(shù)據(jù)之前,程序再次使用pthread_mutex_lock()函數(shù)等待互斥量。一旦獲取了互斥量的使用權(quán),程序就可以在共享內(nèi)存區(qū)域中讀取數(shù)據(jù)。讀取數(shù)據(jù)完成后,程序再次使用pthread_mutex_unlock()函數(shù)釋放互斥量的使用權(quán)。最后,程序解除共享內(nèi)存映射,并刪除共享內(nèi)存文件。
使用互斥量來(lái)同步共享內(nèi)存訪問(wèn)有以下幾點(diǎn)注意事項(xiàng):
1、互斥量需要初始化。在創(chuàng)建互斥量之前,需要使用pthread_mutexattr_init()函數(shù)初始化互斥量屬性,并使用pthread_mutexattr_setpshared()函數(shù)將互斥量屬性設(shè)置為PTHREAD_PROCESS_SHARED,以便多個(gè)進(jìn)程可以共享互斥量。
2、在訪問(wèn)共享內(nèi)存之前,需要使用pthread_mutex_lock()函數(shù)獲取互斥量的使用權(quán)。一旦獲取了互斥量的使用權(quán),程序才能訪問(wèn)共享內(nèi)存。在完成共享內(nèi)存的訪問(wèn)之后,需要使用pthread_mutex_unlock()函數(shù)釋放互斥量的使用權(quán),以便其他進(jìn)程可以訪問(wèn)共享內(nèi)存。
3、互斥量必須存儲(chǔ)在共享內(nèi)存區(qū)域中。在創(chuàng)建互斥量時(shí),需要將其初始化為共享內(nèi)存區(qū)域中的一部分,以便多個(gè)進(jìn)程可以訪問(wèn)同一個(gè)互斥量。
4、程序必須保證互斥量的一致性。多個(gè)進(jìn)程共享同一個(gè)互斥量時(shí),必須保證互斥量的一致性。否則,可能會(huì)導(dǎo)致多個(gè)進(jìn)程同時(shí)訪問(wèn)共享內(nèi)存區(qū)域,導(dǎo)致數(shù)據(jù)錯(cuò)誤或者系統(tǒng)崩潰。
總之,使用互斥量來(lái)同步共享內(nèi)存訪問(wèn)可以有效地避免多個(gè)進(jìn)程同時(shí)訪問(wèn)共享內(nèi)存區(qū)域的問(wèn)題,從而保證數(shù)據(jù)的一致性和程序的穩(wěn)定性。在實(shí)際編程中,需要根據(jù)具體的需求選擇不同的同步機(jī)制,以保證程序的正確性和效率。
3、條件變量同步
在Linux下,可以使用條件變量(Condition Variable)來(lái)實(shí)現(xiàn)多進(jìn)程之間的同步。條件變量通常與互斥量(Mutex)結(jié)合使用,以便在共享內(nèi)存區(qū)域中對(duì)數(shù)據(jù)進(jìn)行同步訪問(wèn)。
條件變量是一種線程同步機(jī)制,用于等待或者通知某個(gè)事件的發(fā)生。當(dāng)某個(gè)進(jìn)程需要等待某個(gè)事件發(fā)生時(shí),它可以通過(guò)調(diào)用pthread_cond_wait()函數(shù)來(lái)阻塞自己,并將互斥量釋放。一旦事件發(fā)生,其他進(jìn)程就可以通過(guò)調(diào)用pthread_cond_signal()或pthread_cond_broadcast()函數(shù)來(lái)通知等待線程。等待線程接收到通知后,會(huì)重新獲取互斥量,并繼續(xù)執(zhí)行。
在共享內(nèi)存通信中,可以使用條件變量來(lái)實(shí)現(xiàn)進(jìn)程之間的同步。具體操作步驟如下:
初始化互斥量和條件變量。在創(chuàng)建共享內(nèi)存之前,需要使用pthread_mutexattr_init()和pthread_condattr_init()函數(shù)分別初始化互斥量屬性和條件變量屬性。然后,需要使用pthread_mutexattr_setpshared()和pthread_condattr_setpshared()函數(shù)將互斥量屬性和條件變量屬性設(shè)置為PTHREAD_PROCESS_SHARED,以便多個(gè)進(jìn)程可以共享它們。
等待條件變量。在讀取共享內(nèi)存之前,程序可以使用pthread_cond_wait()函數(shù)等待條件變量。調(diào)用pthread_cond_wait()函數(shù)會(huì)自動(dòng)釋放互斥量,并阻塞當(dāng)前進(jìn)程。一旦其他進(jìn)程發(fā)送信號(hào)通知條件變量發(fā)生變化,等待線程就會(huì)重新獲得互斥量,并繼續(xù)執(zhí)行。
發(fā)送信號(hào)通知條件變量變化。在向共享內(nèi)存中寫(xiě)入數(shù)據(jù)之后,程序可以使用pthread_cond_signal()或pthread_cond_broadcast()函數(shù)發(fā)送信號(hào)通知條件變量發(fā)生變化。調(diào)用pthread_cond_signal()函數(shù)會(huì)發(fā)送一個(gè)信號(hào)通知等待線程條件變量發(fā)生變化,而調(diào)用pthread_cond_broadcast()函數(shù)會(huì)向所有等待線程發(fā)送信號(hào)通知條件變量發(fā)生變化。
使用條件變量來(lái)同步共享內(nèi)存訪問(wèn)有以下幾點(diǎn)注意事項(xiàng):
1、程序必須使用互斥量來(lái)保護(hù)共享內(nèi)存。在使用條件變量之前,程序必須先獲取互斥量的使用權(quán),以便保護(hù)共享內(nèi)存區(qū)域中的數(shù)據(jù)不被多個(gè)進(jìn)程同時(shí)訪問(wèn)。
2、程序必須保證條件變量的一致性。多個(gè)進(jìn)程共享同一個(gè)條件變量時(shí),必須保證條件變量的一致性。否則,可能會(huì)導(dǎo)致多個(gè)進(jìn)程同時(shí)訪問(wèn)共享內(nèi)存區(qū)域,導(dǎo)致數(shù)據(jù)錯(cuò)誤或者系統(tǒng)崩潰。
3、程序必須正確使用條件變量。在使用條件變量時(shí),需要正確地使用pthread_cond_wait()、pthread_cond_signal()和pthread_cond_broadcast()函數(shù),否則可能會(huì)導(dǎo)致死鎖或者其他問(wèn)題。
4、程序必須正確處理信號(hào)。當(dāng)調(diào)用pthread_cond_wait()函數(shù)時(shí),程序可能會(huì)因?yàn)榻邮盏叫盘?hào)而提前返回,此時(shí)程序需要正確地處理信號(hào)。
下面是一個(gè)使用條件變量實(shí)現(xiàn)進(jìn)程間共享內(nèi)存同步的示例代碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#define SHM_SIZE 4096
#define SHM_NAME "/myshm"
#define SEM_NAME "/mysem"
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
char buffer[SHM_SIZE];
} shm_t;
int main(int argc, char *argv[]) {
int fd, pid;
shm_t *shm;
pthread_mutexattr_t mutex_attr;
pthread_condattr_t cond_attr;
// 創(chuàng)建共享內(nèi)存區(qū)域
fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0) {
perror("shm_open");
exit(1);
}
// 設(shè)置共享內(nèi)存大小
if (ftruncate(fd, sizeof(shm_t)) < 0) {
perror("ftruncate");
exit(1);
}
// 將共享內(nèi)存映射到進(jìn)程地址空間
shm = mmap(NULL, sizeof(shm_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm == MAP_FAILED) {
perror("mmap");
exit(1);
}
// 初始化互斥量屬性和條件變量屬性
pthread_mutexattr_init(&mutex_attr);
pthread_condattr_init(&cond_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
// 初始化互斥量和條件變量
pthread_mutex_init(&shm->mutex, &mutex_attr);
pthread_cond_init(&shm->cond, &cond_attr);
// 創(chuàng)建子進(jìn)程
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) {
// 子進(jìn)程寫(xiě)入共享內(nèi)存
sleep(1);
pthread_mutex_lock(&shm->mutex);
sprintf(shm->buffer, "Hello, world!");
pthread_cond_signal(&shm->cond);
pthread_mutex_unlock(&shm->mutex);
exit(0);
} else {
// 父進(jìn)程讀取共享內(nèi)存
pthread_mutex_lock(&shm->mutex);
pthread_cond_wait(&shm->cond, &shm->mutex);
printf("Received message: %s\n", shm->buffer);
pthread_mutex_unlock(&shm->mutex);
}
// 刪除共享內(nèi)存
if (shm_unlink(SHM_NAME) < 0) {
perror("shm_unlink");
exit(1);
}
return 0;
}
在這個(gè)示例中,程序創(chuàng)建了一個(gè)名為"/myshm"的共享內(nèi)存區(qū)域,并將其映射到進(jìn)程地址空間中。然后,程序使用互斥量和條件變量來(lái)同步進(jìn)程之間的訪問(wèn)共享內(nèi)存區(qū)域。具體來(lái)說(shuō),父進(jìn)程首先鎖定互斥量,然后等待條件變量的信號(hào)。子進(jìn)程等待一秒鐘后,鎖定互斥量,將"Hello, world!"字符串寫(xiě)入共享內(nèi)存區(qū)域,然后發(fā)送條件變量信號(hào),并釋放互斥量。此時(shí),父進(jìn)程將收到條件變量信號(hào)并鎖定互斥量,讀取共享內(nèi)存區(qū)域中的內(nèi)容,并釋放互斥量。
需要注意的是,在使用條件變量時(shí),我們需要遵循一些規(guī)則來(lái)保證程序的正確性,如在等待條件變量時(shí)必須鎖定互斥量,并使用while循環(huán)來(lái)檢查條件變量的值是否滿(mǎn)足要求,等待條件變量信號(hào)的線程必須在等待之前鎖定互斥量,在等待之后解鎖互斥量,等待條件變量信號(hào)的線程可能會(huì)因?yàn)榻邮盏叫盘?hào)而提前返回等等。
總之,使用互斥量和條件變量來(lái)實(shí)現(xiàn)進(jìn)程間共享內(nèi)存通信的同步,需要我們仔細(xì)考慮程序中所有可能出現(xiàn)的情況,并正確地使用互斥量和條件變量函數(shù)來(lái)同步進(jìn)程之間的訪問(wèn)。
小結(jié)
好了,這次我們通過(guò)Linux下進(jìn)程間共享內(nèi)存通信方式講解了常用的同步機(jī)制:信號(hào)量、互斥鎖、條件變量。希望對(duì)小伙伴們?cè)谌粘5木幊坍?dāng)中有所幫助。