1、pipe/msgqueue/sems/shm相关代码的实现及总结
管道:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // 用于创建匿名管道
int main(){
int fds[2];
char buf[100];
int len;
// 创建匿名管道
if(pipe(fds) == -1){
printf("创建失败!\n");
perror("make pipe");
exit(1);
}
// 从标准输入中读取数据
while(fgets(buf,100,stdin)) {
len = strlen(buf);
// fd[0] 表示读端 fd[1] 表示写端
// 将数据写入到管道中
if( write(fds[1], buf ,len ) != len) {
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// 从管道中读取数据
if((len = read(fds[0], buf, 100)) == -1) {
perror("read from pipe");
break;
}
// 往标准输出写数据
if(write(1, buf, len) != len) {
perror("write to stdout");
break;
}
}
}
消息队列(msgqueue):一种带有数据标识的特殊管道,就像多条并存的管道
使用方法:
发送者:获取消息队列ID—>将数据放入一个带有标识符的结构体中,发送消息给消息队列。
接收者:获取消息队列ID—>将制定标识的消息读出
相关代码:
/**********************************************************
* Author : WangWei
* Email : 872408568@qq.com
* Last modified : 2019-05-05 21:17:56
* Filename : fifo_1.c
* Description : fifo_1 通过FIFO从键盘接收数据,并发送
* *******************************************************/
#include "fifo.h"
int main()
{
// int access(const char *filenpath, int mode);
// 确定文件或文件夹的访问权限。即,检查某个文件的存取方式,比如说是只读方式、只写方式等.
// 如果指定的存取方式有效,则函数返回0,否则函数返回-1。
if(access(FIFO,F_OK)) {
mkfifo(FIFO, 0644);
}
int fifo = open(FIFO, O_WRONLY);
char buf[20];
bzero(buf, 20);
fgets(buf, 20, stdin);
int n = write(fifo, buf, strlen(buf)); // 将数据写入FIFO
printf("已发送:%d 字节\n",n);
return 0;
}
/**********************************************************
* Author : WangWei
* Email : 872408568@qq.com
* Last modified : 2019-05-05 21:34:52
* Filename : fifo_2.c
* Description : 通过FIFO从fifo_1中接收数据并打印出来
* *******************************************************/
#include "fifo.h"
int main()
{
if(access(FIFO, F_OK)) {
mkfifo(FIFO, 0644);
}
int fifo = open(FIFO, O_RDONLY); // 以制度方式打开管道
char buf[20];
bzero(buf, 20);
read(fifo, buf, 20);
printf("新消息: %s \n", buf);
return 0;
}
信号量(sems):用来卸掉各进程或线程间工作的。
临界资源:多个进程或现场可能同时访问的资源,访问这些资源的代码称为临界代码,临界代码所在区域被称为临界区域。
共享内存(shm):最快的进程间通信方式(少了两步用户态与内核态之间的数据拷贝过程)
原理:开辟一块物理内存空间,将这块内存映射到进程的虚拟地址空间进行操作,若多个进程映射连接同一块物理内存,则通过这块物理内存实现进程间通信。
操作步骤;
创建共享内存(shmget)->将共享内存映射到虚拟地址空间(shmat)——>直接通过虚拟地址进行内存操作(memcpy)——>解除映射关系(shmdt)——>删除共享内存(shmctl)
共享内存生命周期随内核
相关代码:
发送端:
#include "comm.h"
#include <signal.h>
int shmid;
void rmid(int sig) {
// 信号来了就删除SHM
shmctl(shmid, IPC_RMID, NULL); // shmctl 获取/设置共享内存相关属性
}
int main()
{
signal(SIGINT, rmid);
key_t key = ftok(PATHNAME, PROJ_ID);
shmid = shmget(key, SHMSZ, IPC_CREAT|0666);
char *p = shmat(shmid, NULL, 0);
bzero(p, SHMSZ);
pid_t pid = getpid(); // server将自身PID放入shm的钱4个字节中
memcpy(p, &pid, sizeof(pid_t));
fgets(p + sizeof(pid_t), SHMSZ, stdin); // 从键盘将数据填入SHM中
pause(); // 等待client的信号去删除shm
return 0;
}
接收端:
#include "comm.h"
#include <signal.h>
int shmid;
void rmid(int sig) {
// 信号来了就删除SHM
shmctl(shmid, IPC_RMID, NULL); // shmctl 获取/设置共享内存相关属性
}
int main()
{
signal(SIGINT, rmid);
key_t key = ftok(PATHNAME, PROJ_ID);
shmid = shmget(key, SHMSZ, IPC_CREAT|0666);
char *p = shmat(shmid, NULL, 0);
bzero(p, SHMSZ);
pid_t pid = getpid(); // server将自身PID放入shm的钱4个字节中
memcpy(p, &pid, sizeof(pid_t));
fgets(p + sizeof(pid_t), SHMSZ, stdin); // 从键盘将数据填入SHM中
pause(); // 等待client的信号去删除shm
return 0;
}
[root@Centos7 shm]# cat client.c
#include "comm.h"
int main(int argc, char **argv) {
key_t key = ftok(PATHNAME, PROJ_ID); // 将路径名和项目标识符转换为shm id
int shmid = shmget(key, SHMSZ, 0666);
char *p = shmat(shmid, NULL, 0);
printf("from SHM: %s", p+sizeof(pid_t));
kill(*((pid_t *) p), SIGINT);
shmdt(p);
return 0;
}
2、ipcs -q/m/s与ipcrm -q/m/s的使用及总结
ipcs命令
ipcs -a :显示全部可以显示的信息
ipcs -q:显示活动的消息队列
ipcs -m:显示活动的共享内存信息
ipcs -s:显示活动的信号量信息
ipcrm命令:
ipcrm -m id:删除共享内存标识
ipcrm -M key:删除由关键字创建的共享内存标识
ipcrm -q id :删除消息队列标识 id和其相关的消息队列和数据结构
ipcrm -Q key:删除由关键字key创建的消息队列和其相关的消息队列和数据结构
ipcs -s id:删除信号标识符id和其相关的信号量集及数据结构
ipcs -S key:删除由关键字key创建的信号量标识及其相关的信号量集及数据结构
3、将二元信号量P/V操作,封装成动态/静态库,并分别使用并测试
P操作:程序进入临界区申请资源的动作。
V 操作:程序离开临界区释放资源的动作。
动态库: 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
静态库:静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
生成静态库:
① gcc -c xxx.c -o xxx.o
② ar -rc libxxx.a xxx.o xxx.o
③ gcc main.c libxxx.a
//gcc main.c -lxxx
(标准版测试运行:gcc main.c -lxxx -L .)
动态库 (共享库):
生成动态库:
gcc -fPIC -shared -o libxxx.so xxx.c
(其中 -fPIC 是未知无关)。
4、同步与互斥概念原理,生产者消费者原理。
进程同步
进程同步也是进程之间直接的制约关系,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系来源于他们之间的合作。
比如说进程A需要从缓冲区读取进程B产生的信息,当缓冲区为空时,进程B因为读取不到信息而被阻塞。而当进程A产生信息放入缓冲区时,进程B才会被唤醒。概念如图所示。
进程互斥
进程互斥是进程之间的间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待。只有当使用临界资源的进程退出临界区后,这个进程才会解除阻塞状态。
比如进程B需要访问打印机,但此时进程A占有了打印机,进程B会被阻塞,直到进程A释放了打印机资源,进程B才可以继续执行。概念如图所示。
生产者消费者原理:
问题描述:生产者-消费者问题是一个经典的进程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制。本作业要求设计在同一个进程地址空间内执行的两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来
这里生产者和消费者是既同步又互斥的关系,首先只有生产者生产了,消费着才能消费,这里是同步的关系。但他们对于临界区的访问又是互斥的关系。因此需要三个信号量empty和full用于同步缓冲区,而mut变量用于在访问缓冲区时是互斥的。