华清远见25072班I/O学习day5
重点内容:
共享内存:
1>原理图:
2> 有关共享内存的相关API
key_t ftok(const char *pathname, int proj_id);
功能:通过给定的文件或目录路径(必须存在)以及一个随机数据来生成一个key值,用于创建IPC对象 ftok("\", 'k')
参数1:已经存在的文件路径,一般我们使用 "\"
参数2:一个任意给定的随机数,只需要低8位即可
返回值:成功返回ipc对象的key值,失败返回(key_t)-1并置位错误码
int shmget(key_t key, size_t size, int shmflg);
功能:将想要共享的size大小的物理内存映射成一个共享内存页表,表以PAGE_SIZE为单位 参数1:创建共享内存的key值,可以是IPC_PRIVATE,也可以是由ftok创建出来的
参数2:要申请的大小,以页为单位,向上取整
参数3:创建标识
IPC_CREAT:打开一个key所对应的共享内存,如果不存在则创建一个共享内存 IPC_EXCL:确保创建了一个共享内存
0664:表示创建的权限
返回值:成功返回创建的共享内存的id,失败返回-1并置位错误码
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:将共享内存的地址,映射到进程的地址空间中(将共享内存的起始地址返回到程序中)
参数1:共享内存的id号
参数2:共享内存起始地址偏移页,一般填NULL,让系统自动分配一个合适的偏移页
参数3:对共享内存的操作方式
0:表示读写的方式
SHM_RDONLY:表示对共享内存只读权限
返回值:成功返回共享内存映射的起始地址,失败返回(void*)-1,并置位错误码
int shmdt(const void *shmaddr);
功能:将共享内存的地址,与进程断开连接
参数:指向共享内存的指针
返回值:成功返回0,失败返回-1并置位错误码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:用于控制共享内存的相关操作,主要用于删除共享内存
参数1:共享内存的id号
参数2:要执行的操作,删除共享内存该值为 IPC_RMID
参数3:如果参数2是删除共享内存,则该参数可以忽略填NULL即可
返回值:成功返回0,失败返回-1并置位错误码
信号量集(信号灯集)
1> 信号量集主要完成的是多个进程间同步的问题(多个进程有先后顺序的执行)
2> 信号量集的原理图:
3> 信号量集相关API
key_t ftok(const char *pathname, int proj_id);
功能:通过给定的文件或目录路径(必须存在)以及一个随机数据来生成一个key值,用于创建IPC对象 ftok("\", 'k')
参数1:已经存在的文件路径,一般我们使用 "\"
参数2:一个任意给定的随机数,只需要低8位即可
返回值:成功返回ipc对象的key值,失败返回(key_t)-1并置位错误码
int semget(key_t key, int nsems, int semflg);
功能:通过给定的key值创建一个系统5信号量集容器
参数1:key值,可以是IPC_PRIVATE,也可以是使用ftok创建出来的key值
参数2:信号量集中的信号的个数
参数3:创建标识
IPC_CREAT:打开一个信号量集,如果不存在,则创建一个信号量集
IPC_EXCL:确保创建出一个新的信号量集
0664:权限
返回值:成功返回创建出来的信号量集的id号,失败返回-1并置位错误码
int semop(int semid, struct sembuf *sops, size_t nsops);
功能:对信号量数组进行操作函数
参数1:信号量数组id号
参数2:操作,是一个结构体包含如下数据
unsigned short sem_num; /* 要操作的资源编号,下标从0开始 */
short sem_op; /* 要执行的操作,负数表示申请资源操作,正数表示释放资源操作 */ short sem_flg; /* 是否阻塞,0表示阻塞执行,IPC_NOWAIT表示非阻塞执行 */
参数3:需要执行的资源个数
返回值:成功返回0.失败返回-1并置位错误码
eg:申请0号信号灯的资源
struct sembuf op;
op.sem_num = 0; //要操作的信号号
op.sem_op = -1; //要进行的操作
op.sem_flg = 0; //阻塞形式申请
semop(semid, &op, 1);
int semctl(int semid, int semnum, int cmd, ...);
功能:对信号量数组进行控制函数,该函数可以有三个参数也可以有四个参数,取决于cmd 参数1:要操作的信号量数组id
参数2:要被操作的信号灯编号
参数3:操作指令
IPC_RMID:表示删除信号量数组,此时参数2可以忽略,参数4可以不填
SETVAL:表示要给编号为semnum的那个信号灯进行设置值,此时需要填写参数4,参数4是一个共用体
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) */
};
参数4:取决于参数3
返回值:成功返回0失败返回-1并置位错误码
eg:删除信号量数组:
semctl(semid, 0, IPC_RMID); 给编号为0的信号灯设置初始值为 1
union semun us;
us.val = 1;
semctl(semid, 0, SETVAL, us)
作业:
1> 使用已经封装好的信号量数组,实现三个进程的同步执行,进程1打印A、进程2打印B,进程3打印C,最终效果是:
ABCABCABCABCABC...
test.c源码:
#include <25072head.h>
#include"sem.h"
#define SEM_A 0
#define SEM_B 1
#define SEM_C 2
// 打印次数
// 进程1:打印A
void processA(int semid) {
while(1)
{
// 等待A的信号量
P(semid, SEM_A);
printf("A");
fflush(stdout); // 立即刷新输出缓冲区
// 允许B打印
V(semid, SEM_B);
usleep(300000);
}
}// 进程2:打印B
void processB(int semid) {
while(1)
{
// 等待B的信号量
P(semid, SEM_B);
printf("B");
fflush(stdout);
// 允许C打印
V(semid, SEM_C);
usleep(300000);
}
}
// 进程3:打印C
void processC(int semid) {
while(1)
{
// 等待C的信号量
P(semid, SEM_C);
printf("C");
fflush(stdout);
// 允许A打印,形成循环
V(semid, SEM_A);
usleep(300000);
}
}
int main(int argc, const char *argv[])
{
int semid;
semid=sem_create(3);
if(semid==-1)
{
perror("semid error");
return -1;
}
pid_t pid1=-1;
pid1=fork();
pid_t pid2=-1;
pid_t pid3=-1;
pid2=fork();
pid3=fork();
if(pid1>0)
{
wait(NULL);
}
else if(pid1==0)
{
processA(semid);
exit(EXIT_SUCCESS);
}
if(pid2>0)
{
wait(NULL);
}
else if(pid2==0)
{
processB(semid);
exit(EXIT_SUCCESS);
}
if(pid3>0)
{
wait(NULL);
}else if(pid3==0)
{
processC(semid);
exit(EXIT_SUCCESS);
}
sem_del(semid);
return 0;
}
sem.h源码:
#ifndef __MYSEM_H__
#define __MYSEM_H__
#include<25072head.h>
//用于semctl函数的共用体
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) */
};
//1.创建并打开一个信号量数组
//参数:要创建出来的信号量集中信号灯的个数
//返回值:成功打开或创建的信号量数组id
int sem_create(int semcount);//2.申请某一个信号灯的资源的操作
//参数:信号灯集的id号
// 要操作的信号灯的编号
int P(int semid, int semnum);//3.释放某一个信号灯的资源的操作
//参数:信号灯集的id号
// 要操作的信号灯的编号
int V(int semid, int semnum);//4.删除信号灯集的操作
int sem_del(int semid);
#endif
sem.c源码:
#include"sem.h"
//定义初始化某个信号灯的函数
int init_semnum(int semid, int semnum)
{
//1.获取要设置的值
int val = 0;
printf("请输入第%d号灯的初始值:", semnum+1);
scanf("%d", &val);
getchar();//2.将输入的值设置到灯中
union semun buf;
buf.val = val; //将输入的值放入共用体变量中
//3.调用semctl设置初始值
if(semctl(semid, semnum, SETVAL, buf) ==-1)
{
perror("semctl error");
return -1;
}return 0;
}
//1.创建并打开一个信号量数组
//参数:要创建出来的信号量集中信号灯的个数
//返回值:成功打开或创建的信号量数组id
int sem_create(int semcount)
{
//1.创建一个key值
key_t key = ftok("/", 's');
if(key == -1)
{
perror("ftok error");
return -1;
}//2.打开或创建一个信号量数组
int semid = semget(key, semcount, IPC_CREAT|IPC_EXCL|0664);
if(semid == -1)
{
//对错误码进行判断
if(errno == EEXIST)
{
//表示信号量数组已经存在,直接打开即可,无需给里面的信号灯初始化
semid = semget(key, semcount, IPC_CREAT);
return semid;
}
perror("semget error");
return -1;
}
//3.给每个信号灯进行初始化值
for(int i=0; i<semcount; i++)
{
//给semid信号量数组的下标为i的灯设置初始值
init_semnum(semid, i);
}
//4.将semid返回
return semid;
}
//2.申请某一个信号灯的资源的操作
//参数:信号灯集的id号
// 要操作的信号灯的编号
int P(int semid, int semnum)
{
//定义一个用于操作的结构体变量
struct sembuf buf;
buf.sem_num = semnum; //要操作的信号灯编号
buf.sem_op = -1; //表示要进行申请资源操作
buf.sem_flg = 0; //阻塞形式申请资源
//调用semop函数申请资源,如果该信号灯没有资源,
//会在该函数处阻塞
if(semop(semid, &buf, 1) == -1)
{
perror("P error");
return -1;
}
//成功申请返回0
return 0;
}
//3.释放某一个信号灯的资源的操作
//参数:信号灯集的id号
// 要操作的信号灯的编号
int V(int semid, int semnum)
{
//定义一个用于操作的结构体变量
struct sembuf buf;
buf.sem_num = semnum; //要操作的信号灯编号
buf.sem_op = 1; //表示要进行释放资源操作
buf.sem_flg = 0; //阻塞形式申请资源//调用semop函数申请资源,如果该信号灯没有资源,
//会在该函数处阻塞
if(semop(semid, &buf, 1) == -1)
{
perror("P error");
return -1;
}//成功申请返回0
return 0;
}//4、删除信号灯集的操作
int sem_del(int semid)
{
if(semctl(semid, 0, IPC_RMID)==-1)
{
perror("del error");
return -1;
}//正常删除
printf("删除成功\n");
return 0;
}
2> 思维导图
3> 牛客网