进程间通信:
·早期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;
}