進程間通信:
·早期UNIX進程間通信方式:
無名管道(pipe)
有名管道(fifo)
信號(signal)
·System V IPC:
共享內存(share memory)
消息隊列(message queue)
信號燈集(semaphore set)
·套接字(socket)
·早期UNIX進程間通信方式:
無名管道:
特點:
1.只能用於具有親緣關係的進程之間的通信
2.單工的通信模式,具有固定的讀端和寫端
3.無名管道創建時會返回兩個文件描述符,分別用於讀寫管道
4.無名管道內的數據是存在內存中的,讀取後就會清空
無名管道創建:
pipe.c
#include <unistd.h>
int pipe(int pfd[2]);
成功時返回0,失敗返回EOF
pfd包含兩個元素的整形數組,用來保存文件描述符
pfd[0]用於讀管道,pfd[1]用於寫管道
示例:
pipe.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid1,pid2;
char buf[32];
int pfd[2];
if (pipe(pfd) < 0)
{
perror("pipe");
exit(-1);
}
if ((pid1 = fork()) < 0)
{
perror("fork");
exit(-1);
}
else if(pid1 == 0)
{
strcpy(buf,"I'm process1");
write(pfd[1],buf,32);
exit(0);
}
else
{
if ((pid2 = fork()) < 0)
{
perror("fork");
exit(-1);//表示程序異常退出。
}
else if (pid2 == 0)
{
sleep(1);
strcpy(buf,"I'm process2");
write(pfd[1],buf,32);
}
else
{
wait(NULL);
read(pfd[0],buf,32);
printf("%s\n",buf);
wait(NULL);
read(pfd[0],buf,32);
printf("%s\n",buf);
}
}
return 0;
}
寫端存在:
·有數據 read返回實際讀取的字節數
·無數據 進程阻塞
寫端不存在:
·有數據 read返回實際讀取的字節數
·無數據 read返回0
讀端存在:
·有空間 write返回實際寫入的字節數
·無空間 進程阻塞
讀端不存在:
·有空間
·無空間
管道斷裂!
有名管道:
特點:
1.對應管道文件,可用於任意進程之間通信
2.打開管到時可指定讀寫方式
3.通過文件Io操作,內容存放在內存中
有名管道創建:
#include <unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *path,mode_t mode);
成功時返回0,失敗返回EOF
path創建的管道文件路徑
mode管道文件的權限,如0666
示例:
create_fifo.c
read_fifo.c
write_fifo.c
信號:
信號機制:
·信號是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式
·linux內核通過信號通知用戶進程,不同的信號類型代表不同的事件
·linux對早期unix信號機制進行了擴展
·進程對信號有不同的響應方式
1.缺省方式
2.忽略信號
3.捕捉信號
常用信號:
詳情見有道雲 或 百度百科搜索 信號機制會有詳細說明
進程間通信四—信號章節
信號相關命令:
kill [-signal] pid
默認發送SIGTERM
-sig可指定信號
pid指定發送對象
killall [-u user | prog]
prog 指定進程名
user 指定用戶名
例子:
kill -9 6437
給進程組發信號
kill -9 -8126
給除init進程和當前進程外的其他進程發信號
kill -9 -1
killall a.out
killall -u linux
信號相關函數:
kill / raise
#include <unistd.h>
#inclde <signal.h>
int kill(pid_t pid,int sig);
int raise(int sig);
用於向任何進程組或進程發送信號。
成功返回0,失敗返回EOF
pid接收進程的進程號:
0代表同組進程 -1代表所有進程
sig信號類型
alarm / pause
int alarm(unsigned char seconds);
成功時返回上個定時器的剩餘時間,失敗返回EOF
seconds定時器的時間
一個進程中只能設定一個定時器,時間到時產生SIGALRM
int pasue(void);
進程一直阻塞,直到被信號中斷
被信號中斷後返回-1,errno爲EINTR
示例:
alarm.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
alarm(3);
pause();
printf("i have been waken up\n");
return 0;
}
設置信號響應方式:signal
#include <unistd.h>
#include <signal.h>
void (*signal(int signo,void(*handler)(int)))(int);
成功時返回原先的信號處理函數,失敗返回SIG_ERR
signo要設置的信號類型
handler指定的信號處理函數;SIG_DFL代表缺省方式;SIG_IGN代表忽略信號
只需要設置一次就一直有效
示例:
signal.c
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void handler(int signo)
{
if(signo == SIGINT)
{
printf("i have got SIGINT\n");
}
if(signo == SIGQUIT)
{
printf("i have got SIGQUIT\n");
}
}
int main()
{
signal(SIGINT,handler);
signal(SIGQUIT,handler);
while(1)
{
pause();
}
return 0;
}
System V IPC
IPC對象包含:
·共享內存
·消息隊列
·信號燈集
每個IPC對象有唯一的ID
IPC對象創建後一直存在,直到被顯示地刪除
每個IPC對象有一個關聯的KEY
ipcs / ipcrm
ipcs 命令 查看 System V 的IPC對象
ipcrm 命令刪除一個或更多的消息隊列、信號量集或者共享內存標識。
不同進程通過KEY值能夠打開同一個IPC對象
key爲0 是私有對象
生成key值——ftok()函數
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const cahr *path,int proj_id);
成功時返回合法key值,失敗返回EOF
path存在且可訪問的文件路徑
proj_id用於生成key的數字,不能爲0
通過路徑與proj_id兩者的第八位進行移位組合,生成key
1.共享內存:
- 共享內存是一種最爲高效的進程間通信方式,進程可以直接讀寫內存,而不需要任何數據拷貝
- 共享內存在內核空間創建,可被進程映射到用戶空間訪問,使用靈活
- 由於多個進程可同時訪問共享內存,因此需要同步和互斥機制的配合使用
共享內存使用步驟
- 創建/打開共享內存
- 映射共享內存,即把指定的共享內存映射到進程的地址空間用於訪問
- 讀寫共享內存
- 撤銷共享內存映射
- 刪除共享內存對象
1.共享內存創建—shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,int size,int shmflg);
成功返回共享內存的id,失敗返回EOF
key 和共享內存關聯的key,IPC_PRIVATE或ftok生成
shmflg 共享內存標誌位 IPC_CREAT | 0666
size 生成共享內存大小
shmflg:
0:取共享內存標識符,若不存在則函數會報錯
IPC_CREAT:當shmflg&IPC_CREAT爲真時,如果內核中不存在鍵值與key相等的共享內存,則新建一個共享內存;如果存在這樣的共享內存,返回此共享內存的標識符
PC_CREAT|IPC_EXCL:如果內核中不存在鍵值 與key相等的共享內存,則新建一個共享內存;如果存在這樣的共享內存則報錯
示例:
private_shmget.c
創建一個私有的共享內存,大小512字節,權限0666
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
int main()
{
int shmid;
if((shmid = shmget(IPC_PRIVATE,512,066)) < 0)
{
perror("shmget\n");
exit(-1);
}
return 0;
}
創建/打開一個和key關聯的共享內存,大小1024,權限0666
key_shmget.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
key_t key;
int shmid;
if((key = ftok(".",'m')) == -1)
{
perror("ftok");
exit(-1);
}
if((shmid = shmget(key,1024,IPC_CREAT|0666)) < 0)
{
perror("shmget");
exit(-1);
}
}
2.共享內存映射—shmat
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid,const void *shmaddr,int shmflg);
成功返回映射後的地址,失敗返回(void*)-1
shmid 要映射的共享內存id
shmaddr 映射後的地址,NULL表示由系統自動映射
shmflg 標誌位0表示可讀寫;SHM_RDONLY表示只讀
3.共享內存撤銷映射—shmdt
#include <sys/ipc.h>
#include <sys/shm.c>
int shmdt(void *shmaddr);
成功返回0,失敗返回EOF
不適用共享內存時應撤銷映射
進程結束時自動撤銷
4.共享內存控制—shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmct(int shmid,int cmd,struct shmid_ds *buf);
成功時返回0,失敗返回EOF
shmid 要操作的共享內存id
cmd 要執行的操作 :
IPC_STAT:得到共享內存的狀態,把共享內存的shmid_ds結構複製到buf中
IPC_SET:改變共享內存的狀態,把buf所指的shmid_ds結構中的uid、gid、mode複製到共享內存的shmid_ds結構內
IPC_RMID:刪除這片共享內存
buf 保存或設置共享內存屬性的地址
只是添加刪除標記,並不是真正刪除
共享內存注意事項
每塊共享內存大小有限制
ipcs –l
cat /proc/sys/kernel/shmmax
共享內存刪除的時間點
shmctl(shmid,IPC_RMID,NULL)添加刪除標記
nattach 變成0時真正刪除
2.消息隊列:
消息隊列是System V IPC對象的一種
消息隊列由消息隊列ID來唯一標識
消息隊列就是一個消息的列表。用戶可在消息隊列中添加消息,讀取消息等
消息隊列使用步驟
- 打開/創建消息隊列 msgget
- 向消息隊列發送消息 msgsnd
- 從消息隊列接收消息 msgrcv
- 控制消息隊列 msgctl
1.消息隊列創建/打開—msgget
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key,int msgflg);
成功時返回消息隊列的id,失敗返回EOF
key 和消息隊列關聯的key IPC_PRIVATE 或 ftok
msgflg 標誌位,消息隊列的建立標誌和存取權限。
IPC_CREAT | 0666
IPC_CREAT如果內核中沒有此隊列,則創建它。
IPC_EXCL當和IPC_CREAT一起使用時,如果隊列已經存在,則失敗。
如果單獨使用IPC_CREAT,則msgget()要麼返回一個新創建的消息隊列的標識符,要麼返回具有相同關鍵字值的隊列的標識符。如果IPC_EXCL和IPC_CREAT一起使用,則msgget()要麼創建一個新的消息隊列,要麼如果隊列已經存在則返回一個失敗值-1。IPC_EXCL單獨使用是沒有用處的。
2.消息發送—msgsnd
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid,const void *msgp,size_t size,int msgflg);
成功時返回0,失敗返回-1
msgid 消息隊列id
msgp 消息緩衝區地址
size 消息正文長度
msgflg 標誌位0或IPC_NOWAIT
消息格式:
- 通信雙方首先定義好統一的消息格式
- 用戶根據應用需求定義結構體類型
- 首成員類型爲log,代表消息類型(正整數)
- 其他成員都屬於消息正文
3.消息接收
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid,void *msgp,size_t size,long msgtype,int msgflg);
成功時返回接收到的消息長度,失敗返回-1
msgid 消息隊列id
msgp 消息緩衝區地址
size 指定接收的消息長度
msgtype 指定接收的消息類型
msgflg 標誌欸0或IPC_NOWAIT
4.控制消息隊列
#include <sys/ipc.h>
#include <sys/msg.h>
int msgct(int msgid,int cmd,struct msqid_ds *buf);
成功返回0,失敗返回-1
msgid 消息隊列id
cmd 要執行的操作 IPC_STAT / IPC_SET / IPC_RMID
buf 存放消息隊列屬性的地址
示例:
要求:兩個進程通過消息隊列輪流將鍵盤輸入的字符串發送給對方,接收並打印對方發送到消息,輸入quit後退出
clientA.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct
{
long mytype;
char mtext[64];
}MSG;
#define LEN (sizeof(MSG) - sizeof(long))
#define TypeA 100
#define TypeB 200
int main()
{
key_t key;
int msgid;
MSG buf;
if((key = ftok(".",'q')) == -1)
{
perror("ftork");
exit(-1);
}
if((msgid = msgget(key,IPC_CREAT | 0666)) < 0)
{
perror("msgget");
exit(-1);
}
while(1)
{
buf.mytype = TypeB;
printf("input > ");
fgets(buf.mtext,64,stdin);
msgsnd(msgid,&buf,LEN,0);
if(strcmp(buf.mtext,"quit\n") == 0)break;
if(msgrcv(msgid,&buf,LEN,TypeA,0) < 0)
{
perror("msgrcv");
exit(-1);
}
printf("recv from clientB : %s",buf.mtext);
}
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct
{
long mytype;
char mtext[64];
}MSG;
#define LEN (sizeof(MSG) - sizeof(long))
#define TypeA 100
#define TypeB 200
int main()
{
key_t key;
int msgid;
MSG buf;
if((key = ftok(".",'q')) == -1)
{
perror("ftork");
exit(-1);
}
if((msgid = msgget(key,IPC_CREAT | 0666)) < 0)
{
perror("msgget");
exit(-1);
}
while(1)
{
if(msgrcv(msgid,&buf,LEN,TypeB,0) < 0)
{
perror("msgrcv");
exit(-1);
}
printf("recv from clientA : %s",buf.mtext);
buf.mytype = TypeA;
printf("input > ");
fgets(buf.mtext,64,stdin);
msgsnd(msgid,&buf,LEN,0);
if(strcmp(buf.mtext,"quit\n") == 0)break;
}
return 0;
}
3.信號燈
信號燈也叫信號量,用於進程/線程同步或互斥的機制
信號燈的類型:
- Posix 無名信號燈
- Posix 有名信號燈
- System V 信號燈
信號燈的含義:
計數信號燈
System V IPC 信號燈特點:
- System V 信號燈是一個或多個計數信號燈的集合
- 可同時操作集合中的多個信號燈
- 申請多個資源時避免死鎖
System V 信號燈使用步驟:
1.打開/創建信號燈 semget
2.信號燈初始化 semctl
3.P/V操作 semop
4.刪除信號燈 semctl
信號燈創建/打開—semget
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
成功返回信號燈id,失敗返回-1
key 和消息隊列關聯的key IPC_PRIVATE 或 ftok
nsems 集合中包含的技術信號燈個數
semflg 標誌位 IPC_CREAT | 0666 IPC_EXCL
信號燈集合是從0開始
信號燈控制——semctl
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid,int semnum,int cmd, /*union semun arg*/);
成功返回0,失敗返回EOF
semid 要操作的信號燈集id
semnum 要操作的集合中的信號燈編號
cmd 執行的操作 SETVAL IPC_RMID
cmd命令:
·IPC_STAT讀取一個信號量集的數據結構semid_ds,並將其存儲在semun中的buf參數中。
·IPC_SET設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。
·IPC_RMID將信號量集從內存中刪除。
·GETALL用於讀取信號量集中的所有信號量的值。
·GETNCNT返回正在等待資源的進程數目。
·GETPID返回最後一個執行semop操作的進程的PID。
·GETVAL返回信號量集中的一個單個的信號量的值。
·GETZCNT返回正在等待完全空閒的資源的進程數目。
·SETALL設置信號量集中的所有的信號量的值。
·SETVAL設置信號量集中的一個單獨的信號量的值。
union semun 取決於cmd:
union semun {
short val; /*SETVAL用的值*/
struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds結構*/
unsigned short* array; /*SETALL、GETALL用的數組值*/
struct seminfo *buf; /*爲控制IPC_INFO提供的緩存*/
} arg;
信號燈P/V操作——semop
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid,struct sembuf *sops,unsigned nsops);
成功返回0,失敗返回-1
semid 要操作的信號燈集id
sops 描述對信號燈操作的結構體(數組)
nsops 要操作的信號燈個數
信號燈操作結構體——sembuf
struct sembuf
{
short semnum;
short sem_op;
short sem_flg;
};
semnum 信號燈編號
sem_op -1:P操作,1:V操作
sem_flg 0/IPC_NOWAIT
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define N 64
#define READ 0
#define WRITE 1
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *bufs;
};
void init_sem(int semid,int s[],int n)
{
int i;
union semun myun;
for(i = 0;i < n;i++)
{
myun.val = s[i];
semctl(semid,i,SETVAL,myun);
}
}
void pv(int semid,int num,int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid,&buf,1);
}
int main()
{
int shmid,semid,s[] = {0,1};
pid_t pid;
key_t key;
char *shmaddr;
if((key = ftok(".",'s')) == -1)
{
perror("ftok");
exit(-1);
}
if((shmid = shmget(key,N,IPC_CREAT | 0666)) < 0)
{
perror("shmget");
exit(-1);
}
if((semid = semget(key,2,IPC_CREAT | 0666)) < 0)
{
perror("semget");
goto _error1;
}
init_sem(semid,s,2);
if((shmaddr = (char *)shmat(shmid,NULL,0)) == (char *)-1)
{
perror("shmaddr");
goto _error2;
}
if((pid = fork()) < 0)
{
perror("fork");
goto _error2;
}
else if(pid == 0)
{
char *p,*q;
while(1)
{
pv(semid,READ,-1);
p = q = shmaddr;
while(*q)
{
if(*q != ' ')
{
*p++ = *q;
}
q++;
}
*p = '\0';
printf("%s",shmaddr);
pv(semid,WRITE,1);
}
}
else
{
while(1)
{
pv(semid,WRITE,-1);
printf("input > ");
fgets(shmaddr,N,stdin);
if(strcmp(shmaddr,"quit\n") == 0)break;
pv(semid,READ,1);
}
kill(pid,SIGUSR1);
}
_error1:
shmctl(shmid,IPC_RMID,NULL);
_error2:
semctl(semid,0,IPC_RMID);
return 0;
}