System V 中的的三種通訊機制
<1>msg_ids消息隊列:
消息隊列就是一個消息的鏈表。能夠把消息看做一個記錄,具備特定的格式以及特定的優先級。對消息隊列有寫權限的進程能夠向中按照必定的規則添加新消息;對消息隊列有讀權限的進程則能夠從消息隊列中讀走消息。消息隊列是隨內核持續的,記錄消息隊列的數據結構位於內核中,只有在內核重起或者顯示刪除一個消息隊列時,該消息隊列纔會真正被刪除。
//comm.h
#pragma once
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <errno.h>
#define _PROJ_NAME_ "/home/shihao/Linux/Class8/msg_ids/tmp"
#define _PROJ_ID_ 0x66
#define _SIZE_ 1024
#define _SERVER_TYPE_ 1
#define _CLIENT_TYPE_ 2
typedef struct msgbuf
{
long mtype;
char mtext[_SIZE_];
}msgbuf;
static int creatFun( int flags);
int creat_msg_queue();
int get_msg_queue();
int destory_msg_queue(int msg_id);
int recv_msg(int msg_id, int t, char * msg);
int send_msg(int msg_id, int t, const char * msg);
//comm.c
#include "comm.h"
static int creatFun( int flags)
{
key_t _key = ftok(_PROJ_NAME_,_PROJ_ID_);
if (_key == -1)
{
perror( "ftok" );
return -1;
}
int msg_id = msgget(_key,flags);
if (msg_id == -1)
{
perror( "msgget" );
return -2;
}
return msg_id;
}
int creat_msg_queue()
{
int flags = IPC_CREAT | IPC_EXCL | 0644;
return creatFun(flags);
}
int get_msg_queue()
{
int flags = IPC_CREAT;
return creatFun(flags);
}
int destory_msg_queue(int msg_id)
{
if (msgctl(msg_id,IPC_RMID,NULL) == -1)
{
perror( "msgctl" );
return -3;
}
return 0;
}
int recv_msg(int msg_id, int t, char * msg)
{
msgbuf _msg;
_msg.mtype = t;
memset(_msg.mtext, '\0' ,sizeof (_msg.mtext));
if (msgrcv(msg_id,&_msg,sizeof (_msg.mtext),t,0) < 0)
{
perror( "msgrcv" );
return -1;
}
else
{
strcpy(msg,_msg.mtext);
}
return 0;
}
int send_msg(int msg_id, int t, const char * msg)
{
msgbuf _msg;
_msg.mtype = t;
strncpy(_msg.mtext,msg,strlen(msg)+1);
if (msgsnd(msg_id,&_msg,sizeof (_msg.mtext),0) == -1)
{
perror( "msgrcv" );
return -1;
}
return 0;
}
//server.c
#include "comm.h"
int main()
{
int msg_id = creat_msg_queue();
char buf[_SIZE_];
while (1)
{
memset(buf, '\0' ,sizeof (buf));
recv_msg(msg_id,_CLIENT_TYPE_,buf);
printf( "client->server : %s\n" ,buf);
printf( "please input\n" );
fflush(stdout);
memset(buf, '\0' ,sizeof (buf));
read(0,buf, sizeof (buf)-1);
send_msg(msg_id,_SERVER_TYPE_,buf);
}
destory_msg_queue(msg_id);
return 0;
}
//client.c
#include "comm.h"
int main()
{
int msg_id = get_msg_queue();
char buf[_SIZE_];
while (1)
{
printf( "please input\n" );
fflush(stdout);
memset(buf, '\0' ,sizeof (buf));
read(0,buf, sizeof (buf)-1);
send_msg(msg_id,_CLIENT_TYPE_,buf);
memset(buf, '\0' ,sizeof (buf));
recv_msg(msg_id,_SERVER_TYPE_,buf);
printf( "sever->client :" );
printf( "%s\n" ,buf);
}
return 0;
}
<2>sem_ids信號量:
信號量與其餘進程間通訊方式不大相同,它主要提供對進程間共享資源訪問控制機制。至關於內存中的標誌,進程能夠根據它斷定是否可以訪問某些共享資源,同時,進程也能夠修改該標誌。除了用於訪問控制外,還可用於進程同步。信號燈有如下兩種類型:
①二值信號量:最簡單的信號量形式,信號量的值只能取0或1,相似於互斥鎖。 注:二值信號量 可以實現互斥鎖的功能,但二者的關注內容不一樣。信號量 強調共享資源,只要共享資源可用,其餘進程一樣能夠修改信號量 的值;互斥鎖更強調進程,佔用資源的進程使用完資源後,必須由進程自己來解鎖。
②計算信號量:信號量的值能夠取任意非負值(固然受內核自己的約束)。
//test.h
#pragma once
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define _PATH_ "/home/shihao/Linux/Class10/tmp"
#define _ID_ 0x66
#define _SIZE_ 100
typedef union semun
{
int val;
struct semid_ds* buf;
unsigned short * array;
struct seminfo* _buf;
}semun;
static int commFun( int nsems, int semflags);
int create_sem_set(int nsems);
int get_sem_set();
int init_sem_set(int sem_id, int semnum, int val);
int destory_sem_set(int sem_id);
static int commOp( int sem_id, int semnum,int op);
int P(int sem_id, int which);
int V(int sem_id, int which);
//test.c
#include "comm.h"
static int commFun( int nsems, int semflags)
{
key_t _key = ftok(_PATH_,_ID_); //creat key
if (_key == -1)
{
perror( "ftok" );
return -1;
}
int sem_id = semget(_key,nsems,semflags);
if (sem_id == -1)
{
perror( "semget" );
return -1;
}
else
{
return sem_id;
}
}
int create_sem_set(int nsems)
{
int semflags = IPC_CREAT | IPC_EXCL | 0644;
return commFun(nsems,semflags);
}
int get_sem_set()
{
int semflags = IPC_CREAT;
return commFun(0,semflags);
}
int init_sem_set(int sem_id, int semnum, int val)
{
semun _un;
_un.val = val;
if (semctl(sem_id,semnum,SETVAL,_un) == -1)
{
perror( "semctl" );
return -1;
}
return 0;
}
int destory_sem_set(int sem_id)
{
if (semctl(sem_id,0,IPC_RMID) == -1)
{
perror( "semctl" );
return -1;
}
return 0;
}
static int commOp( int sem_id, int semnum,int op)
{
struct sembuf _sem;
_sem.sem_num = semnum;
_sem.sem_op = op;
_sem.sem_flg = 0;
if (semop(sem_id,&_sem,1) == -1)
{
perror( "semop" );
return -1;
}
return 0;
}
/*struct sembuf
{
short semnum; /*信號量集合中的信號量編號,0表明第1個信號量*/
short val; /*若val>0進行V操做信號量值加val,表示進程釋放控制的資源 */
/*若val<0進行P操做信號量值減val,若(semval-val)<0(semval爲該信號量值),則調用進程阻塞,直到資源可用;若設置IPC_NOWAIT不會睡眠,進程直接返回EAGAIN錯誤*/
/*若val==0時阻塞等待信號量爲0,調用進程進入睡眠狀態,直到信號值爲0;若設置IPC_NOWAIT,進程不會睡眠,直接返回EAGAIN錯誤*/
short flag; /*0 設置信號量的默認操做*/
/*IPC_NOWAIT設置信號量操做不等待*/
/*SEM_UNDO 選項會讓內核記錄一個與調用進程相關的UNDO記錄,若是該進程崩潰,則根據這個進程的UNDO記錄自動恢復相應信號量的計數值*/
};
*/
int P(int sem_id, int which)
{
commOp(sem_id,which,-1);
return 0;
}
int V(int sem_id, int which)
{
commOp(sem_id,which,1);
return 0;
}
//test.c
#include "comm.h"
int main()
{
int sem_id = create_sem_set(1);
init_sem_set(sem_id,0,1);
pid_t id = fork();
if (id == 0)
{ //child
while (1)
{
int child_sem_id = get_sem_set();
P(child_sem_id,0);
printf( "A" );
fflush(stdout);
usleep(rand()%1234567);
printf( "A" );
fflush(stdout);
V(child_sem_id,0);
}
}
else if (id > 0)
{ //father
while (1)
{
P(sem_id,0);
printf( "B" );
fflush(stdout);
usleep(rand()%1111111);
printf( "B" );
fflush(stdout);
V(sem_id,0);
}
}
}
<3>shm_ids共享內存區:
共享內存能夠說是最有用的進程間通訊方式,也是最快的IPC形式。兩個不一樣進程A、B共享內存的意思是,同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A能夠即時看到進程B對共享內存中數據的更新,反之亦然。因爲多個進程共享同一塊內存區域,必然須要某種同步機制,互斥鎖和信號量均可以。
採用共享內存通訊的一個顯而易見的好處是效率高,由於進程能夠直接讀寫內存,而不須要任何數據的拷貝。對於像管道和消息隊列等通訊方式,則須要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據[1]: 一次從輸入文件到共享內存區,另外一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,並不老是讀寫少許數據後就解除映射,有新的通訊時,再從新建 立共享內存區域。而是保持共享區域,直到通訊完畢爲止,這樣,數據內容一直保存在共享內存中,並無寫回文件。共享內存中的內容每每是在解除映射時才寫回 文件的。所以,採用共享內存的通訊方式效率是很是高的。
能夠說,共享內存是一種最爲高效的進程間通訊方式,由於進程能夠直接讀寫內存,不須要任何數據的複製。爲了在多個進程間交換信息,內核專門留出了一塊內存區,這段內存區能夠由須要訪問的進程將其映射到本身的私有地址空間。所以,進程就能夠直接讀寫這一內存區而不須要進行數據的複製,從而大大提升了效率。固然,因爲多個進程共享一段內存,所以也須要依靠某種同步機制,如互斥鎖和信號量等。其原理示意圖如圖1所示。
圖1 共享內存原理示意圖
共享內存的實現分爲兩個步驟:第一步是建立共享內存,這裏用到的函數是shmget(),也就是從內存中得到一段共享內存區域;第二步是映射共享內存,也就是把這段建立的共享內存映射到具體的進程空間中,這裏使用的函數是shmat()。到這裏,就可使用這段共享內存了,也就是可使用不帶緩衝的I/O讀寫命令對其進行操做。除此以外,還有撤銷映射的操做,其函數爲shmdt()。
//shm.h
#pragma once
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
#define _PATH_ "/home/shihao/Linux/Class11/shm/1"
#define _ID_ 0x66
#define _SIZE_ 4*1024
static int comm_shm( int size, int shmflag);
int create_shm();
int get_shm();
void * at_shm(int shm_id);
int dt_shm(char * shmaddr);
int destory_shm(int shm_id);
//shm.c
#include "shm.h"
static int comm_shm( int size, int shmflag)
{
// key_t _key = ftok(_PATH_,_ID_);
// if(_key == -1)
// {
// perror("ftok");
// return -1;
// }
int shm_id = shmget((key_t)123456,_SIZE_,shmflag);
if (shm_id == -1)
{
perror( "getshm" );
return -1;
}
return shm_id;
}
int create_shm()
{
int shmflag = IPC_CREAT | IPC_EXCL | 0664;
return comm_shm(_SIZE_,shmflag);
}
int get_shm()
{
int shmflag = IPC_CREAT;
return comm_shm(0,shmflag);
}
void * at_shm(int shm_id)
{
return shmat(shm_id,NULL,0);
}
int dt_shm(char * shmaddr)
{
return shmdt(shmaddr);
}
int destory_shm(int shm_id)
{
return shmctl(shm_id,IPC_RMID,NULL);
}
//server.c
#include "shm.h"
int main()
{
int shm_id = create_shm();
char * buf = (char *)at_shm(shm_id);
size_t index = 0;
while (1)
{
buf[index++] = 'A' ;
buf[index] = '\0' ;
sleep(1);
if (index == 15)
break ;
}
dt_shm(buf);
printf( "HaHa i am here\n" );
destory_shm(shm_id);
}
//client.c
#include "shm.h"
int main()
{
int shm_id = get_shm();
char * buf = (char *)at_shm(shm_id);
size_t index = 0;
while (1)
{
printf( "%s\n" ,buf);
sleep(1);
if (index++ == 20)
break ;
}
dt_shm(buf);
printf( "HaHa i am here\n" );
}
1、mmap系統調用
mmap將一個文件或者其它對象映射進內存。文件被映射到多個頁上,若是文件的大小不是全部頁的大小之和,最後一個頁不被使用的空間將會清零。munmap執行相反的操做,刪除特定地址區域的對象映射。
當使用mmap映射文件到進程後,就能夠直接操做這段虛擬地址進行文件的讀寫等操做,沒必要再調用read,write等系統調用.但需注意,直接對該段內存寫時不會寫入超過當前文件大小的內容.
採用共享內存通訊的一個顯而易見的好處是效率高,由於進程能夠直接讀寫內存,而不須要任何數據的拷貝。對於像管道和消息隊列等通訊方式,則須要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據:一次從輸入文件到共享內存區,另外一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,並不老是讀寫少許數據後就解除映射,有新的通訊時,再從新創建共享內存區域。而是保持共享區域,直到通訊完畢爲止,這樣,數據內容一直保存在共享內存中,並無寫回文件。共享內存中的內容每每是在解除映射時才寫回文件的。所以,採用共享內存的通訊方式效率是很是高的。
基於文件的映射,在mmap和munmap執行過程的任什麼時候刻,被映射文件的st_atime可能被更新。若是st_atime字段在前述的狀況下沒有獲得更新,首次對映射區的第一個頁索引時會更新該字段的值。用PROT_WRITE 和 MAP_SHARED標誌創建起來的文件映射,其st_ctime 和 st_mtime在對映射區寫入以後,但在msync()經過MS_SYNC 和 MS_ASYNC兩個標誌調用以前會被更新。
用法:
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *start, size_t length);
返回說明:
成功執行時,mmap()返回被映射區的指針,munmap()返回0。失敗時,mmap()返回MAP_FAILED[其值爲(void *)-1],munmap返回-1。
errno被設爲如下的某個值
EACCES:訪問出錯
EAGAIN:文件已被鎖定,或者太多的內存已被鎖定
EBADF:fd不是有效的文件描述詞
EINVAL:一個或者多個參數無效
ENFILE:已達到系統對打開文件的限制
ENODEV:指定文件所在的文件系統不支持內存映射
ENOMEM:內存不足,或者進程已超出最大內存映射數量
EPERM:權能不足,操做不容許
ETXTBSY:已寫的方式打開文件,同時指定MAP_DENYWRITE標誌
SIGSEGV:試着向只讀區寫入
SIGBUS:試着訪問不屬於進程的內存區
參數:
start:映射區的開始地址。
length:映射區的長度。
prot:指望的內存保護標誌,不能與文件的打開模式衝突。是如下的某個值,能夠經過or運算合理地組合在一塊兒
PROT_EXEC //頁內容能夠被執行
PROT_READ //頁內容能夠被讀取
PROT_WRITE //頁能夠被寫入
PROT_NONE //頁不可訪問
flags:指定映射對象的類型,映射選項和映射頁是否能夠共享。它的值能夠是一個或者多個如下位的組合體
MAP_FIXED //使用指定的映射起始地址,若是由start和len參數指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。若是指定的起始地址不可用,操做將會失敗。而且起始地址必須落在頁的邊界上。
MAP_SHARED //與其它全部映射這個對象的進程共享映射空間。對共享區的寫入,至關於輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新。
MAP_PRIVATE //創建一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。這個標誌和以上標誌是互斥的,只能使用其中一個。
MAP_DENYWRITE //這個標誌被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要爲這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會獲得保證。當交換空間不被保留,同時內存不足,對映射區的修改會引發段違例信號。
MAP_LOCKED //鎖定映射區的頁面,從而防止頁面被交換出內存。
MAP_GROWSDOWN //用於堆棧,告訴內核VM系統,映射區能夠向下擴展。
MAP_ANONYMOUS //匿名映射,映射區不與任何文件關聯。
MAP_ANON //MAP_ANONYMOUS的別稱,再也不被使用。
MAP_FILE //兼容標誌,被忽略。
MAP_32BIT //將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標誌只在x86-64平臺上獲得支持。
MAP_POPULATE //爲文件映射經過預讀的方式準備好頁表。隨後對映射區的訪問不會被頁違例阻塞。
MAP_NONBLOCK //僅和MAP_POPULATE一塊兒使用時纔有意義。不執行預讀,只爲已存在於內存中的頁面創建頁表入口。
fd:有效的文件描述詞。若是MAP_ANONYMOUS被設定,爲了兼容問題,其值應爲-1。
offset:被映射對象內容的起點。
二. 系統調用mmap()用於共享內存的兩種方式:
(1)使用普通文件提供的內存映射:適用於任何進程之間;此時,須要打開或建立一個文件,而後再調用mmap();典型調用代碼以下:
fd=open(name, flag, mode);
if(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
經過mmap()實現共享內存的通訊方式有許多特色和要注意的地方
(2)使用特殊文件提供匿名內存映射:適用於具備親緣關係的進程之間;因爲父子進程特殊的親緣關係,在父進程中先調用mmap(),而後調用fork()。那麼在調用fork()以後,子進程繼承父進程匿名映射後的地址空間,一樣也繼承mmap()返回的地址,這樣,父子進程就能夠經過映射區域進行通訊了。注意,這裏不是通常的繼承關係。通常來講,子進程單獨維護從父進程繼承下來的一些變量。而mmap()返回的地址,卻由父子進程共同維護。
對於具備親緣關係的進程實現共享內存最好的方式應該是採用匿名內存映射的方式。此時,沒必要指定具體的文件,只要設置相應的標誌便可.
三. mmap進行內存映射的原理
1.在用戶虛擬地址空間中尋找空閒的知足要求的一段連續的虛擬地址空間,爲映射作準備(由內核mmap系統調用完成)
2.創建虛擬地址空間和文件或設備的物理地址之間的映射(設備驅動完成)
3.
實際訪問新映射的頁面(由缺頁中斷完成)