一、管道通信
1、无名管道
1、什么是管道:
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
2、管道的特点:
1、单向性,一端写,一段读
2、管道中数据是一次性的,读完就没有了
3、管道中没有数据的时候,读取时会阻塞,直到管道中有数据
3、管道的用途:一般用于父子进程之间的通信,因此又称无名管道
4、写管道的步骤
(1、创建管道
(2、创建子进程(先创建管道的话,子进程会复制管道,此时读写用的是同一个管道)
(3、对父进程和子进程分别进行读或者写操作
(4、进行读或者写之前,先要关闭不使用的管道口
5、pipe函数创建管道
int pipe(int filedis[2])
其中filedis[0]对应读管道
filedis10]对应写管道
6、父子进程之间的管道通信
#include <stdio.h>
#include <unistd.h>
#define SIZE 1024
//子进程进行写
void child_do(int pipefd[])
{
close(pipefd[0]); //关闭读
char buf[SIZE] ={0};
while(1)
{
printf("子进程写入数据\n");
fgets(buf,SIZE,stdin);
ssize_t ret = write(pipefd[1],buf,SIZE);
if(-1 == ret)
{
perror("读失败");
break;
}
}
close(pipefd[1]); //关闭写
}
//父进程进行读
void parent_do(int pipefd[])
{
close(pipefd[1]); //关闭写
char buf[SIZE] = {0};
while(1)
{
ssize_t ret =read(pipefd[0],buf,SIZE);
if(-1 == ret)
{
perror("读失败");
break;
}
//buf[ret] = '\n';
printf("父进程读到的数据:%s",buf);
}
close(pipefd[0]); //关闭读
}
int main()
{
//创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("管道创建失败");
return -1;
}
//创建子进程
pid_t pid = fork();
switch(pid)
{
case 0: //子进程
child_do(pipefd);
break;
case -1: //进程创建失败
perror("进程创建失败");
break;
default: //父进程
parent_do(pipefd);
break;
}
return 0;
}
2、命名管道
1、什么是命名管道,与无名管道的区别
命名管道(FIFO)和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。
2、命名管道的创建
int mkfifo(const char *pathname, mode_t mode);
第一个参数是:创建管道文件的名字(此函数只能在linux系统下创建,window下无法创建,因此不能在共享文件中创建)
第二个参数:创建文件的权限
注意:读、写端无法独立存在,一旦有一端关闭,另外一端就无法使用。
3、因为命名管道创建的是一个文件,需要用到open,read,write这些函数(默认是阻塞型的)
4、命名管道非阻塞的使用
在使用open打开的时候,添加O_NONBLOCK
注意点:当使用非阻塞的时候,一旦文件是空,会返回一个特殊的错误
它本质不是错误,是提示文件空。可以用errno == EAGAIN来判断是空。
5、创建管道文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int fd = mkfifo("myfifo",0766);
if(-1 ==fd)
{
perror("管道文件创建失败");
return -1;
}
return 0;
}
6、写数据
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define SIZE 1024
int main()
{
int fd = open("/home/myfifo",O_WRONLY);
if(-1 == fd)
{
perror("打开文件失败");
return -1;
}
char buf[SIZE] = {0};
while(1)
{
printf("子进程写入数据\n");
fgets(buf,SIZE,stdin);
ssize_t ret = write(fd,buf,strlen(buf));
if(-1 == ret)
{
perror("写文件失败");
return -1;
}
}
close(fd);
return 0;
}
7、读数据
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define SIZE 1024
int main()
{
int fd = open("/home/myfifo",O_RDONLY);
if(-1 == fd)
{
perror("打开文件失败");
return -1;
}
char buf[SIZE+1] = {0};
while(1)
{
ssize_t ret = read(fd,buf,SIZE);
if(-1 == ret)
{
perror("读文件失败");
return -1;
}
buf[ret] = '\n';
printf("父进程读到的数据:%s",buf);
}
close(fd);
return 0;
}
8、非阻塞管道文件读对应的文件
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define SIZE 1024
int main()
{
int fd = open("/home/myfifo",O_RDONLY | O_NONBLOCK);
if(-1 == fd)
{
perror("打开文件失败");
return -1;
}
char buf[SIZE+1] = {0};
while(1)
{
ssize_t ret = read(fd,buf,SIZE);
if(-1 == ret)
{
if(errno == EAGAIN)
{
printf("文件为空\n");
sleep(2);
continue;
}
perror("读文件失败");
return -1;
}
buf[ret] = '\n';
printf("父进程读到的数据:%s",buf);
}
close(fd);
return 0;
}
二、共享内存
1、什么是共享内存
共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。共享内存是有内核空间创建的一块空间。
2、共享内存的特点:
(1、保存在内存中的数据,不删除内存的话是一直存在的
(2、共享内存的大小不是固定的,需要多大就创建多大
3、共享内存的使用步骤
(1、创建共享内存
(2、将内存映射
(3、解除映射(不一定要)
(4、删除共享内存(不一定要)
4、共享内存的创建
int shmget(key_t key, size_t size, int shmflg);
第一个参数:内存空间对应的编号(用于区分不同的共享内存)
第二个参数:想要创建的大小
第三个参数:创建的模式,一般使用IPC_CREAT
返回值:对应内存的标识
5、共享内存的映射
void *shmat(int shmid, const void *shmaddr, int shmflg);
第一个参数:想要映射的内存的标识
第二个参数:想要开辟空间的位置(由于不清楚,一般写NULL)
第三个参数:对应的标志位:一般写0;
返回值:想要内存对应的地址
6、解除映射
int shmdt(const void *shmaddr);
想要解除映射内存的地址
7、删除内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
第一个参数:想要删除的内存的标识
第二个参数:command是要采取的操作,删除用IPC_RMID
第三个参数:buf是一个结构指针,它指向共享内存模式和访问权限的结构。,一般写NULL
三、信息量
1、什么是信息量
信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个进程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。信息量是一种进程同步的操作,主要用于控制临界资源。
2、临界区的概念
临界区域是指执行数据更新的代码需要独占式地执行。
3、信息量的核心操作
p操作:对信息量-1操作
v操作:对信息量+1操作
4、信息量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
5、信息量的创建
int semget(key_t key, int num_sems, int sem_flags);
第一个参数:key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
第二个参数:指定需要的信号量数目,它的值几乎总是1。
第三个参数:是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget函数成功返回一个相应信号标识符(非零),失败返回-1.
6、该函数用来直接控制信号量信息:
int semctl(int sem_id, int sem_num, int command, …);
如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
第一个参数是信号量集IPC标识符。
第二个参数是操作信号在信号集中的编号,第一个信号的编号是0
第三个参数 command通常是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
7、它的作用是改变信号量的值:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
第一个参数 sem_id是由semget返回的信号量标识符,
第二个参数 sembuf结构的定义如下:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
第三个参数 nsops:信号操作结构的数量,恒大于或等于1。
8、买票系统
1、main函数
#include <stdio.h>
#include <sys/shm.h>
#include "ticket.h"
#include "semaphore.h"
#include <time.h>
void sell_ticket(int semid,MSG *pa)
{
srand((unsigned int)time(NULL));
while(1)
{
usleep(100000*(rand()%10+1));
//p操作
semaphore_p(semid);
//买票了
if(0 == pa->ticket)
{
printf("票买完了,请明天再来\n");
semaphore_v(semid); //退出前注意要解锁
break;
}
printf("出票成功,座位号为%d\n",101-(pa->ticket));
pa->ticket -= 1;
//V操作
semaphore_v(semid);
}
}
int main(int argc,char **argv)
{
//创建共享内存
int shmid = shmget((key_t)123,sizeof(MSG),IPC_CREAT);
if(-1 == shmid)
{
perror("共享内存创建失败");
return -1;
}
//映射
MSG *pa = shmat(shmid,NULL,0);
if(NULL == pa)
{
perror("映射失败");
return -1;
}
//创建信号量
int semid = semget((key_t)456,1,IPC_CREAT);
//初始化
if(2 == argc)
{
pa->ticket = 100;
init_smph(semid);
}
//买票
sell_ticket(semid,pa);
sleep(2);
//删除信号量和映射
if(2 == argc )
{
del_smph(semid);
shmdt(pa);
}
return 0;
}
2、.h文件
#ifndef __TICKET__H__
#define __TICKET__H__
typedef struct msg
{
int ticket;
}MSG;
#endif // __TICKET__H__
#ifndef __SEMAPHORE_H__
#define __SEMAPHORE_H__
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <string.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO(Linux specific) */
};
// 信号量初始化
int init_smph(int semid)
{
union semun sem;
sem.val = 1;
// 给信号量 semid进行初始化
int ret = semctl(semid, 0, SETVAL, sem);
if(ret == -1)
{
perror ("semctl");
return -1;
}
return 0;
}
// 信号量的P操作
int semaphore_p(int semid)
{
struct sembuf sem;
memset (&sem, 0, sizeof(sem));
sem.sem_num = 0;
sem.sem_op = -1;
sem.sem_flg = SEM_UNDO;
int ret = semop(semid, &sem, 1);
if (ret == -1)
{
perror ("semop_p");
return -1;
}
return 0;
}
// 信号量的v操作
int semaphore_v(int semid)
{
struct sembuf sem;
memset (&sem, 0, sizeof(sem));
sem.sem_num = 0;
sem.sem_op = 1;
sem.sem_flg = SEM_UNDO;
int ret = semop(semid, &sem, 1);
if (ret == -1)
{
perror ("semop_v");
return -1;
}
return 0;
}
// 信号量的删除
int del_smph(int semid)
{
int ret = semctl(semid, 0, IPC_RMID);
if(ret == -1)
{
perror ("semctl");
return -1;
}
return 0;
}
#endif // __SEMAPHORE_H__