程序間通訊方式
1.無名管道/有名管道
2.訊號
3.共享佇列(system V-IPC)
4.共享記憶體(system V-IPC)
5.訊號量(system V-IPC)
6.套接字
無名管道特徵
1.檔案沒有名字,無法使用open
2.只能用於親緣程序間
3.半雙工工作方式:讀寫端分開
4.寫入操作不具有原子性,會被打斷,因此只能用於一對一的簡單場景
5.不能使用lseek()來定位
相關API
int pipe(int pipefd[2]);
應用模板
int main(int argc, char *argv[])
{
int fd[2];
if (pipe(fd) == -1)
{
fprintf(stderr, "errno:%d,%s", errno, strerror(errno));
exit(-1);
}
pid_t x = fork(); // 建立子程序,繼承pipe的描述符
if (x == 0) // 子程序
{
char *s = "I am the child\n";
write(fd[1], s, strlen(s)); // 1是讀,0是寫
}
if (x > 0) // 父程序
{
char buf[30];
bzero(buf, sizeof(buf));
read(fd[0], buf, sizeof(buf));
printf("from the child:%s", buf);
}
close(fd[0]); // 因為無名,所以無法用open,但還是需要close
close(fd[1]);
return 0;
}
有名管道特徵
1.有名字,且儲存於普通檔案系統中
2.任何有許可權的程序都可以使用open函式獲取FIFO檔案描述符
4.寫入操作具有原子性,支援多寫者同時進行寫操作且資料不會相互踐踏。這是與無名管道的最大區別
5.不能使用lseek()來定位
6.FIFO,最先被寫入的資料,最先被讀出來
相關API
int mkfifo(const char *pathname, mode_t mode);
應用模板
#define FIFO "/tmp/fifo4test"
int main(int argc, char *argv[]) // 傳送的程序
{
if (access(FIFI, F_OK)) // 檢查檔案是否存在,不存在就建立
{
mkfifo(FIFO, 0644);
}
int fifo_fd = open(FIFO, O_RDWR);
char *s = "I am the child\n";
int n = write(fifo_fd, s, strlen(s)); // 1是讀,0是寫
printf("%d bytes have been sended.\n", n);
close(fifo_fd);
return 0;
}
int main(int argc, char *argv[]) // 接收的程序
{
if (access(FIFI, F_OK)) // 檢查檔案是否存在,不存在就建立
{
mkfifo(FIFO, 0644);
}
int fifo_fd = open(FIFO, O_RDWR);
char buf[30];
bzero(buf, sizeof(buf));
read(fifo_fd, buf, sizeof(buf));
printf("from the child:%s", buf);
close(fifo_fd);
return 0;
}
訊號特徵
1.大部分訊號都是非同步
2.linux訊號62個:
1~31是非實時,不可靠訊號,響應不排隊;
如果目標程序沒有及時相應,則隨後到達的同樣的訊號會被丟棄;
每個非實時訊號都對應一個系統事件。
當程序的掛起訊號中含有實時和非實時訊號,則會優先響應實時訊號並從大到小依次響應
34~64實時,可靠訊號,按接收順序排隊
即使相同的實時訊號被同時傳送多次也不會被丟棄,而是依次響應
實時訊號沒有系統事件與之對應
3.對訊號的處理:阻塞,被捕捉並響應(按設定的響應函式或忽略),執行預設動作
相關API
int kill(pid_t pid, int sig); // 向pid程序傳送訊號
void (*sighandler_t)(int); // 設定的函式,sighandler_t 被定義為指向一個接收單個 int 引數(即訊號編號)並返回 void 的函式的指標。這樣的函式通常被稱為“訊號處理函式”或“訊號處理程式”。
sighandler_t signal(int signum, sighandler_t handler); // 接收signum訊號,並執行handler訊號處理函式,一般和kill配套使用
int raise(int sig); // 給自己傳送訊號
int pause(void); // 掛起等待訊號
sigset_t setset; // 建立訊號集
int sigemptyset(sigset_t *set); // 清空訊號集
int sigfillset(sigset_t *set); // 將所有訊號新增到訊號集中
int sigaddset(sigset_t *set, int signum); // 將特定訊號新增到訊號集中
int sigdelset(sigset_t *set, int signum); // 將特定訊號從訊號集中刪除
int sigismember(const sigset_t *set, int signum); // 判斷某特定訊號是否在訊號集中
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // 設定阻塞掩碼
應用模板
void handler(int sig)
// sig是觸發該處理函式的訊號值;
// 子程序會不會繼承父程序的訊號響應函式和阻塞狀態,每個程序都有自己的訊號遮蔽字(signal mask),用於控制哪些訊號在當前是阻塞的(即不會被立即處理)。
// 而且不同的訊號能共享一個處理函式
{
printf("This is a test4signal.sig:%d\n", sig);
}
int main(int argc, char const *argv[])
{
sigset_t set; // 1.建立一個訊號集
sigemptyset(&set); // 清空訊號集
sigaddset(&set, SIGINT); // 2.把需要遮蔽的訊號加入到該集合中
sigprocmask(SIG_BLOCK, &set, NULL); // 3.需要設定該集合的阻塞屬性,第一個引數如果是SIG_UNBLOCK,就解除阻塞
signal(SIGUSR1, handler);
// 接收訊號,第二個引數設定成SIG_IGN是忽略 SIG_DEL是刪除
pause(); // 暫停程序,等待訊號。此處可以用while(1),一直等待訊號
return 0;
}
訊息佇列,共享記憶體,訊號量 (統稱為system-V IPC)
1.訊息佇列,提供帶有資料標識的特殊管道
2.共享記憶體,提供一塊實體記憶體多次對映到不同的程序虛擬空間
3.訊號量,對程序或執行緒的資源進行管理
訊息佇列相關API
key_t ftok(const char *pathname, int proj_id); // 指定路徑和未使用過的整數
int msgget(key_t key, int msgflg); // 獲取訊息佇列的ID
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); // 傳送訊息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); // 接收訊息
int msgctl(int msqid, int cmd, struct msqid_ds *buf); // 設定訊息佇列的屬性
應用模板
// 應用模板:
#define PROJ_PATH "."
#define PROJ_ID 10
#define A2B 1L // 定義訊息標識
#define MSGSZ 100 // 定義訊息長度
;
struct msgbuf // 定義帶標識的訊息結構體
{
long mtype;
char mtext[MSGSZ]; // 訊息最大一般是16384bytes
};
int main()
{
key_t key = ftok(PROJ_PATH, PROJ_ID);
int msgid = msgget(key, IPC_CREAT | 0644);
struct msgbuf buf;
bzero(&buf, sizeof(buf));
buf.mtype = A2B;
strcpy(buf.mtext, "This is a msg test.\n");
msgsnd(msgid, &buf, strlen(buf.mtext), MSGSZ);
// 錯誤處理此處忽略,實際專案需要考慮
// ================下面為從訊息佇列讀取訊息============//
struct msgbuf rec;
bzero(&rec, sizeof(rec));
msgrcv(msgid, &buf, MSGSZ, A2B, 0); // 0表示等待訊息,預設阻塞
printf("recive the message:%s", rec.mtext);
// ================從訊息佇列讀取訊息============//
return 0;
}
共享記憶體相關API
key_t ftok(const char *pathname, int proj_id); // 指定路徑和未使用過的整數
int shmget(key_t key, size_t size, int shmflg); // 獲取的ID
void *shmat(int shmid, const void *shmaddr, int shmflg); // 對共享記憶體進行對映
int shmdt(const void *shmaddr); // 解除對映
int shmctl(int msqid, int cmd, struct msqid_ds *buf); // 設定共享記憶體的屬性
訊號量相關API
key_t ftok(const char *pathname, int proj_id); // 指定路徑和未使用過的整數
int semget(key_t key, int nsems, int semflg); // 獲取訊號量ID
int semop(int semid, struct sembuf *sops, size_t nsops); // 對訊號量進行P/V操作
int semctl(int semid, int semnum, int cmd, ...); // 獲取或設定訊號量的相關屬性
因為systemV IPC 的訊號量用的較少,且程序間通訊一般可以用POSIX的訊號量代替,因此一起簡要總結後者。
POSIX的訊號量分為有名和無名:有名訊號量,是一種特殊的檔案,一般會放在系統的特殊檔案系統/dev/shm中,不同程序間需要約定一個相同的名字,就能透過這種有名訊號量來相互協調;無名訊號量,一般用於一個程序內執行緒間的同步互斥,因為執行緒共享一個記憶體空間。
POSIX有名訊號量API
sem_t *sem;
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); // 建立/開啟一個有名訊號量
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); // 申請資源,即P操作
int sem_post(sem_t *sem); // 釋放資源,即V操作
int sem_close(sem_t *sem); // 關閉
int sem_unlink(const char *name); // 刪除有名訊號量檔案
POSIX無名訊號量API
sem_t *sem;
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); // 申請資源,即P操作
int sem_post(sem_t *sem); // 釋放資源,即V操作
int sem_destroy(sem_t *sem);
/*
有名訊號量和共享記憶體應用模板
#define PROJ_PATH "."
#define PROJ_ID 10
#define SEMNAME "sem4test" // 定義訊息標識
#define SHMSZ 100 // 定義訊息長度
int main()
{
key_t key = ftok(PROJ_PATH, PROJ_ID); // 指定路徑和未使用過的整數
int shm_id = shmget(key, SHMSZ, IPC_CREAT | 0644); // 獲取共享記憶體的ID
char *shmaddr = shmat(shm_id, NULL, 0); // 對共享記憶體進行對映
// 建立POSIX有名訊號量
sem_t *s;
s = sem_open(SEMNAME, O_CREAT, 0644, 0);
while (1)
{
fgets(shmaddr, SHMSZ, stdin);
sem_post(s); // 向訊號量釋放資源,即每次進行操作結束後資源量+1
}
sem_unlink(SEMNAME);
//=========================下半部分為接收訊號=====================//
while (1)
{
sem_post(s); // 向訊號量申請資源,當資訊被讀完,即資訊量為0時被阻塞
printf("recive message:%s", shmaddr);
}
//=========================接收訊號=====================//
sem_close(s);
return 0;
}
尤其注意,System V的訊號量和POSIX的訊號量不是一個概念,它們之間存在明顯的區別。以下是兩者之間的主要區別:
- 來源與標準:
System V訊號量:來源於Unix作業系統的一個分支,即System V版本。
POSIX訊號量:來源於“可移植作業系統介面(Portable Operating System Interface)”標準,這是一個由電氣與電子工程學會(IEEE)開發,並由ISO(國際標準化組織)和IEC(國際電工委員會)採納的國際標準。
- 使用場景:
System V訊號量:常用於程序間的同步。
POSIX訊號量:常用於執行緒間的同步,但也可以用於程序間同步,特別是當使用有名訊號量時。
- 實現與複雜性:
System V訊號量:使用相對複雜,通常涉及多個步驟和結構體(如struct sembuf)。
POSIX訊號量:使用相對簡單,透過單一的sem_open呼叫即可完成訊號量的建立、初始化和許可權設定。
- 儲存位置:
System V訊號量:基於核心,存放在核心空間中。
POSIX訊號量:基於記憶體,訊號量值通常存放在共享記憶體中,有名訊號量透過檔案系統(如/dev/shm)中的特殊檔案來表示。
- 訊號量型別:
System V訊號量:通常作為訊號量集合存在,每個集合可以包含多個訊號量。
POSIX訊號量:有兩種型別——有名訊號量和無名訊號量。有名訊號量透過IPC名字進行程序間同步,無名訊號量則通常用於執行緒間同步。
- 訊號量操作:
System V訊號量:透過semop等系統呼叫來操作訊號量。
POSIX訊號量:透過sem_wait(P操作)、sem_post(V操作)等函式來操作訊號量。
- 標頭檔案與介面:
System V訊號量:使用<sys/sem.h>標頭檔案,介面函式包括semget、semop等。
POSIX訊號量:使用<semaphore.h>標頭檔案,介面函式包括sem_open、sem_wait、sem_post等。
剩下一項套接字,一般用於網路程式設計,即不同主機間的程序通訊,後續補充。