传统的进程间通信
1.有名管道pipe
2.无名管道fifo
3.信号signal
1.无名管道pipe
查看命令: man 2 pipe
头文件:#include <unistd.h>
函数原型: int pipe(int pipefd[2]);
pipefd[2] :无名管道的两个文件描述符,int型的数组,大小为2,pipefd[0]为读端,pipefd[1]为写端
返回值:
成功:0
失败:-1
无名管道的特点:
a、没有名字,因此无法使用open()打开。
b、只能用于亲缘进程间(如父子进程、兄弟进程、祖孙进程等)通信。
c、半双工工作方式,读写端是分开的,pipefd[0]为读端、pipefd[1]为写端。
d、是一种特殊的文件,只存在内存中,由内核进行管理。
e、对于它的读写可以使用文件IO如read、write函数。
f、无名管道的操作属于一次性操作,如果对无名管道进行读操作,数据会被全部读走。
三种通信方式:
单工:固定一种方向进行通信 类似于:广播
半双工:同一时间只能由一端发送到另一端 类似于:对讲机
全双工:通信方向都可以,同时可以发送也可以接收 类似于:电话
特点:既然说是管道,所以可以想象成一条水管,连接两个进程,一个进程负责输入数据,另一个进程负责接收数据,反过来也一样。
所以在无名管道中也一样,无名管道的两端,每一端都可以读和写。
有下面两种情况:
情况一:一个进程,那就是一端读取一端写入。
int main(int argc, const char *argv[])
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe");
return -1;
}
//从写端fd[1]写入数据
write(fd[1], "hello world", 11);
char buf[100] = {0};
//从读端fd[1]读取数据
read(fd[0], buf, sizeof(buf));
printf("buf=%s\n", buf);
return 0;
}
情况二:两个亲缘进程间,若一端为读就要关闭他的写功能,另一端就只能为写关闭读功能。
int main(int argc, const char *argv[])
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0) //子进程
{
//读.先关闭写端
close(fd[1]);
char buf[100] = {0};
while(1)
{
read(fd[0], buf, sizeof(buf));
printf("buf=%s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
else{ //父进程
//写.先关闭读端
close(fd[0]);
char buf[100] = {0};
while(1)
{
gets(buf);
write(fd[1], buf, strlen(buf));
}
}
return 0;
}
注意事项:
- 当管道中无数据时,执行读操作,读操作阻塞。
- 无名管道的大小是固定的,管道一旦满,写操作就会导致进程阻塞。
- 对无名管道的操作,类似一个队列,后写入的数据不会覆盖之前的数据,会在其后面存储,读取完的数据会从管道里面移除。
- 将读端关闭,向无名管道中写数据,管道破裂,进程收到信号(SIGPIPE),默认这个信号会将进程杀死 。
- 当管道中有数据,将写端关闭,读操作可以执行,之后数据读完,可以继续读取(非阻塞),直接返回0。
2.有名管道fifo
有名管道也叫命名管道,是在文件系统目录中存在一个管道文件,是一个文件。
管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。
查看命令:man 3 mkfifo
头文件:
#include <sys/types.h>
#include <sys/stat.h>
3、函数原型:
int mkfifo(const char *pathname, mode_t mode);
功能: 创建有名管道
参数:
pathname: 文件的路径名
mode: 权限
在shell中使用mkfifo命令: mkfifo filename
eg:
mkfifo f1
or
int ret = mkfifo("f1",0666);
if(-1 == ret)
{
perror("mkfifo ");
return -1;
}
特点:
1. 有名管道存在文件系统中,数据存在内存中。
2. 可以用于无亲缘关系的进程。
3. 只有读端和写端同时存在管道才能打开成功。
数据传输特点:
1、读端不存在时,写端写入数据将会阻塞。
2、读端意外结束,写端再写数据将会管道破裂,该进程结束。
3、有名管道的数据存储在内存中,数据交互在内核中。
有名管道和无名管道的异同点
1、相同点
open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的
位置)和尾指针(指向读的位置)指向它。读写数据都是在给内存的操作,并且都是半双工通讯。
2、不同点
有名管道在任意进程之间使用,无名管道在亲缘进程之间使用。
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();//创建进程
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
int fd = open("./f1", O_WRONLY);//打开创建好的管道文件
char buf[100] = {0};
while(1)
{
gets(buf);
write(fd, buf, strlen(buf));//往管道中写入数据
}
close(fd);
}
else{
int fd = open("./f1", O_RDONLY);//另一个进程也打开管道文件,用于通信
char buf[100] = {0};
while(1)
{
read(fd, buf, sizeof(buf));//读取数据
printf("buf=%s\n", buf);
memset(buf, 0, sizeof(buf));
}
close(fd);
}
return 0;
}
3.信号
概念:信号是在软件层次上对中断机制的一种模拟。
kill函数把信号发送给进程或进程组。
raise函数把信号发送给(进程)自身。
头文件:#include <signal.h>
函数:
int kill(pid_t pid, int signo);
int raise(int signo);
成功则返回0, 出错则返回-1
raise(signo); 等价于 kill(getpid(), signo);
alarm();//设置闹钟
头文件:#include <unistd.h>
函数:
unsigned int alarm(unsigned int seconds);
可以为当前进程定义闹钟,时间到了会发出SIGALRM信号。
每个进程只能有一个alarm,当重新定义alarm时,会重新计时。
如果之前定义了一个闹钟,则这次定义返回的是上次闹钟剩余的时间,否则返回0。
例如:
int main(int argc, const char *argv[])
{
alarm(5);//定义第一次闹钟
sleep(2);//延时2秒,实际闹钟还剩3秒
int m = alarm(5);//定义第二次闹钟
printf("m = %d\n", m);//返回值为第一闹钟剩余的时间3秒
while(1);
return 0;
}
该程序7秒后结束,第一个闹钟延时2秒后,遇到第二个闹钟,则刷新,所以2+5=7秒
pause();//程序暂停
pause函数的作用,是让当前进程暂停运行,交出CPU给其他进程去执行。
当前进程进入pause状态后,当前进程会表现为“卡住、阻塞住”。
要退出pause状态,当前进程需要被信号唤醒。
信号的三种处理方式:
1.忽略 2.默认 3.自定义信号处理函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//捕获信号,设置信号的处理方式
//handler: SIG_IGN:忽略
// SIG_DFL:默认
// 自定义的信号处理函数
示例1:
signal(SIGINT,SIG_IGN);//表示遇到ctrl+c结束信号就忽略,继续进行程序。
SIGINT : ctrl + c
SIGQUIT : ctrl +\
SIGTSTP : ctrl + z
示例2:
void handler(int sig)
{
if(sig == SIGQUIT)
{
printf("笑脸\n");
}
else if(sig == SIGTSTP)
{
printf("哭脸\n");
}
}
int main(int argc, const char *argv[])
{
signal(SIGQUIT, handler);
signal(SIGTSTP, handler);
while(1)
{
printf("-----------\n");//没有遇到指定信号时,会一直打印--------
sleep(1);
}
return 0;
}
system V的IPC对象
ipcs命令:
ipcs -m :查询显示当前系统的共享内存
ipcs -q :查询显示当前系统的消息队列
ipcs -s :查询显示当前系统的信号灯集
ipcrm -m shmid:删除某个共享内存
ipcrm -q msgid:删除某个消息队列
ipcrm -s semid:删除某个信号灯集
IPC步骤:
key id
ftok ----> shm_get/msg_get/sem_get ----> shmmat/shmdt/shmctrl
msgctrl/msgsend/msgrecv
semctrl/semop
ftok函数
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
定义:key_t key;
函数原型:key_t ftok(const char *pathname, int proj_id);
功能:得到key值
参数:
pathname: 路径名
proj_id: 1-255
返回值:
成功: key
失败: -1
4.共享内存
是一种通信效率最高的进程间通信方式,
进程间通信时直接访问内存,不需要进行数据的拷贝。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
步骤:
1. ftok
2. shmget
3. shmat
4. 进程间通信 fork.
5. shmdt
6. shmctl
int shmget(key_t key, size_t size, int shmflg);
功能:创建共享内存
参数:
key: key值 or IPC_PRIVATE
size: 共享内存的大小 1024bytes 4096bytes
shmflg: 打开方式 IPC_CREAT|0666
返回值:
成功:shmid
失败:-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能: 创建映射区域
参数:
shmid: shmid值
shmaddr: NULL (默认:自动映射)
shmflg:
SHM_RDONLY: 只读
0 : 读写 (重要!)
返回值:
成功:共享内存的地址
失败:(void *)-1
int shmdt(const void *shmaddr);
功能:解除映射
参数:
shmaddr: 共享内存的地址 shmat的返回值
返回值:
成功:0
失败:-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能: 控制函数
参数:
shmid: shmid值
cmd: IPC_RMID (删除共享内存)
buf: NULL
返回值:
成功:0
失败:-1
示例:
int main(int argc, const char *argv[])
{
//1.ftok得到key值
key_t key = ftok("/", 1);
if(key == -1)
{
perror("ftok");
return -1;
}
//2.创建共享内存得到shmid值
int shmid = shmget(key, 1024, IPC_CREAT|0666);
if(shmid == -1)
{
perror("shmget");
goto xxx;
}
//3.映射, p就是共享内存的地址
char *p = shmat(shmid, NULL, 0);
if(p == NULL)
{
perror("shmat");
goto xxx;
}
//4.进程间通信(亲缘)
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
goto xxx;
}
else if(pid == 0) //子进程
{
//对共享内存写
char buf[100] = {0};
while(1)
{
gets(buf);
strcpy(p, buf);
if(strncmp(buf, "quit", 4) == 0)
{
break;
}
memset(buf, 0, sizeof(buf));
}
}
else{ //父进程
//对共享内存读
while(1)
{
waitpid(-1, NULL, WNOHANG);
printf("p=%s\n", p);
if(strncmp(p, "quit", 4) == 0)
{
break;
}
sleep(1);
}
}
//5.解除映射
int ret = shmdt(p);
if(ret == -1)
{
perror("shmdt");
goto xxx;
}
//6.删除共享内存
ret = shmctl(shmid, IPC_RMID, NULL);
if(ret == -1)
{
perror("shmctl");
goto xxx;
}
xxx:
sleep(2);
char buf[64] = {0};
sprintf(buf, "ipcrm -m %d", shmid);
system(buf);
system("ipcs -m");
return 0;
}
5.消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
步骤:
1. ftok
2. msgget
3. 进程间通信
4. msgsnd
5. msgrcv
6. msgctl
int msgget(key_t key, int msgflg);
功能:创建消息队列
参数:
key: key值
msgflg: IPC_CREAT|0666
返回值:
成功:msgid值
失败:-1
struct msgbuf {
long mtype; /* message type, must be > 0 */ 类型
char mtext[N]; /* message data */ 正文
};
struct msgbuf msg;
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
功能:发送数据
参数:
msgid: msgid值
msgp: &msg (结构体变量的地址)
msgsz: 消息正文的长度 sizeof(msg) - sizeof(long)
msgflg:
IPC_NOWAIT: 非阻塞
0 : 阻塞
返回值:
成功: 0
失败:-1
ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收数据
参数:
msgid: msgid值
msgp: &msg (结构体变量的地址)
msgsz: 消息正文的长度 sizeof(msg) - sizeof(long)
msgtyp: 消息的类型
msgflg:
IPC_NOWAIT: 非阻塞
0 : 阻塞
返回值:
成功: 0
失败: -1
int msgctl(int msgid, int cmd, struct msqid_ds *buf);
功能: 控制函数
参数:
msgid: msgid值
cmd: IPC_RMID (删除消息队列)
buf: NULL
返回值:
成功; 0
失败:-1
6.信号灯集
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
步骤:
1. ftok
2. shmget
3. shmat
* 4. semget
* 5. semctl
6. 进程间通信
* 7. semop
* 8. semctl
9. shmdt
10. shmctl
int semget(key_t key, int nsems, int semflg);
功能:创建信号灯集
参数:
key:ftok的返回值 or IPC_PRIVATE
nsems: 设置的信号灯的数目
semflg:
IPC_CREAT|0666
返回值:
成功:semid
失败:-1
union semun {
int val; /* Value for SETVAL */ 初值
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int semctl(int semid, int semnum, int cmd, ...);
功能:信号灯集控制函数
参数:
semid: semget的返回值
semnum: 要设置的信号灯的编号
cmd:
IPC_RMID: 删除信号灯集
SETVAL: 设置信号灯
...: 共用体的变量 or 不写
返回值:
成功:0
失败:-1
编号 初值
0 sem0 0
1 sem1 1
子进程:写入
sem1:-1
//write
sem0:+1
父进程:读取
sem0:-1
//read
sem1:+1
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对信号灯进行PV操作 (+1 -1)
参数:
semid: semget的返回值
sops: 结构体的变量的地址
struct sembuf{
unsigned short sem_num; /* semaphore number 信号灯的编号 */
short sem_op; /* semaphore operation 信号灯操作-1 +1*/
short sem_flg; /* operation flags 0:阻塞 */
}
nsops: 信号灯的个数 默认就是1
返回值:
成功:0
失败:-1