linux多线(进)程编程——(9)信号量(二)
前言
上一篇文章我们讲解了信号量的基础用法,这一篇文章我们承接上面的内容,给大家进一步提升对信号量的理解。如果没有看过上一篇文章,请大家移步linux多线(进)程编程——(9)信号量(一)
案例:高性能科研设备开发
(1)小明得到了一个由A公司生产的科研设备,该设备会采集大量医学影像的原始数据并且输出,厂家提供了设备中数据采集软件的原始代码。
(2)小明之前做过一个独立的软件,只要获得原始数据就可以处理数据并且输出重建后的医学图像。
(3)由于设备移植,需要更换A公司生产的设备,因此需要提供一个软件用于实时处理A设备的数据并给用户呈现出重建后的医学图像。项目周期特别短,需要一个尽快可行的方案,减少开发周期,及时拿出产品。
(4)后续可能需要更换多家公司的设备。
案例1的需求分析
(1)需要研发一个科研设备的软件,需要实时性,因此优先考虑设备性能。
(2)项目开发周期短,需要尽可能利用已经有的资源(厂家提供的原始代码,已经有的独立软件)。
(3)尽可能减少耦合性,以防后续再次更换设备。(所以我们不能在厂家提供的原始代码上开发,否则没有可移植性)
技术方案
结合上面的需求,小明的方案是,独立运行厂家提供的数据采集程序与小明个人开发的数据处理与图像重建程序。考虑需要传输大量数据,需求高效性,使用共享内存实现两个程序间的数据传输(不了解共享内存的同学请移步:linux多线(进)程编程——(6)共享内存)。仅需要在两个程序间提供数据传输接口即可,代码开发量大大降低,且防止修改代码结构可能造成的一系列问题。为了防止进程间冲突,还需要一个信号量来完成进程间同步。
使用信号量管理共享内存访问的的流程:同一时刻只能有一个进程访问共享内存。当信号量为值0时,证明共享内存被占用,值为1时证明当前没有进程操作共享内存。
数据发送端开发
接下来我们直接上代码展示,首先是厂家提供的源代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>#define PACKET_SIZE (512 * 512 * sizeof(double) + 1)// 检查设备上电状态,返回0表示正常
int device_check(void);// 外部实现的函数,将数据存储到*_buf指向的空间中,空间动态分配
// 返回值:传输数据的字节数
int recv_data(char** _buf);int getData(void) {int ret = 0;ret = device_check(); // 硬件设备检查if(ret != 0) return ret;/* another fun *//* over */char* buf = NULL;while(1) {ret = recv_data(&buf); // 从硬件外设中获取数据if(ret < 0) return ret;/* another process *//* over */free(buf); // 释放空间buf = NULL;}return 0;
}int main() {int ret = getData(); // 数据获取函数启动if(ret <= 0) { // 消息错误打印// 。。。}return 0;
}
为了能向其他进程传输数据,在这里我们需要提供一个初始化接口initTrans
,用于初始化信号量与共享内存:
/*
功能:数据传输初始化
参数:
dataSize_: [in] 共享内存大小
shmbuf_: [out] 指向共享内存的映射地址
shmid_: [out] 共享内存ID
semid_: [out] 信号量ID
*/
int initTrans(int dataSize_, char** shmbuf_, int* shmid_, int* semid_) {*shmid_ = shmget(1, dataSize_, 0666|IPC_CREAT);if(*shmid_ == -1) printf("initTrans: shared memory init fail\n");else printf("initTrans: shared memory init success\n");*shmbuf_ = (char*)shmat(*shmid_, NULL, 0);if(*shmbuf_ == NULL) printf("initTrans: shared memory map fail\n");else printf("initTrans: shared memory map success\n");*semid_ = semget(1, 1, 0666|IPC_CREAT);if(*semid_ == -1) printf("initTrans: semaphore init fail\n");else printf("initTrans: semaphore init success\n");union sempun {int val; // 用于SETVALstruct semid_ds *buf; // 用于IPC_STAT/IPC_SETunsigned short *array; // 用于GETALL/SETALL} arg;arg.val = 1;if(-1 == semctl(*semid_, 0, SETVAL, arg) ) printf("initTrans: sem init val set fail\n");printf("initTrans: sem init val is : %d\n", semctl(*semid_, 0, GETVAL));printf("initTrans: Trans init success!\n");return 0;
}
还需要一个数据发送接口dataTrans
,向共享内存写入数据。
/*
功能:数据传输
参数:
shmbuf_: [in] 共享内存的映射地址
srcbuf_: [in] 存放要传输数据的缓冲区
dataSize_: [in] 要传输的数据大小
semid_: [in] 信号量ID
*/
int dataTrans(char* shmbuf_, char* srcbuf_, int dataSize_ ,int semid_) {printf("dataTrans: data is sending\n");struct sembuf stbu = {0};stbu.sem_op = -1;stbu.sem_num = 0;stbu.sem_flg = 0;semop(semid_, &stbu, 1);memcpy(shmbuf_, srcbuf_, dataSize_);stbu.sem_op = 1;semop(semid_, &stbu, 1);printf("dataTrans: data send over\n");return 0;
}
最后提供一个资源回收的接口deinitTrans
:
/*
功能:资源回收
参数:
shmbuf_: [in] 共享内存的映射地址
shmid_: [in] 共享内存ID
semid_: [in] 信号量ID
*/
void deinitTrans(char* shmbuf_, int shmid_, int semid_) {if(-1 == shmdt(shmbuf_)) printf("deinitTrans: share memory detach fail\n");else printf("deinitTrans: share memory detach success\n");if(-1 == shmctl(shmid_, IPC_RMID, NULL))printf("deinitTrans: share memory delete fail\n");else printf("deinitTrans: share memory delete success\n");if( -1 == semctl(semid_, 0, IPC_RMID))printf("deinitTrans: sem delete fail\n");else printf("deinitTrans: sem delete success\n");
}
将我们的接口接入程序后,数据发送端变为:
int getData(void) {int ret = 0;ret = device_check();if(ret != 0) return ret;/* another fun */int shmid = -1, semid = -1;char* shmbuf = NULL;initTrans(PACKET_SIZE, &shmbuf, &shmid, &semid);/* over */char* buf = NULL;while(1) {ret = recv_data(&buf);if(ret < 0) return ret;/* another process */dataTrans(shmbuf, buf, ret, semid);sleep(1);/* over */free(buf);buf = NULL;}deinitTrans(shmbuf, shmid, semid);return 0;
}
数据接收端开发
小明开发的程序框架为:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>#define PACKET_SIZE (512 * 512 * sizeof(double) + 1)// 用于处理数据,滤波,重建等
void dataProcess(char* _iniData, char* proData);
// 开启图窗显示图像
void imageDisplay(char* _iamgeData);void app() {char* databuf = malloc(PACKET_SIZE);char* imagebuf = malloc(PACKET_SIZE);while(1) {dataProcess(databuf, imagebuf);imageDisplay(imagebuf);}
}int main() {app();return 0;
}
与发送端相同,我们需要一个初始化接口initTransRcv
,但在这里我们不需要设置信号量的初始值:
/*
功能:数据传输初始化
参数:
dataSize_: [in] 共享内存大小
shmbuf_: [out] 指向共享内存的映射地址
shmid_: [out] 共享内存ID
semid_: [out] 信号量ID
*/
int initTransRcv(int dataSize_, char** shmbuf_, int* shmid_, int* semid_) {printf("initTransRcv: wait send port init\n"); // 等待发送端初始化sleep(3);*shmid_ = shmget(1, dataSize_, 0666|IPC_CREAT);if(*shmid_ == -1) printf("initTransRcv: shared memory init fail\n");else printf("initTransRcv: shared memory init success\n");*shmbuf_ = (char*)shmat(*shmid_, NULL, 0);if(*shmbuf_ == NULL) printf("initTransRcv: shared memory map fail\n");else printf("initTransRcv: shared memory map success\n");*semid_ = semget(1, 1, 0666|IPC_CREAT);if(*semid_ == -1) printf("initTransRcv: semaphore init fail\n");else printf("initTransRcv: semaphore init success\n");printf("initTransRcv: Trans init success!\n");return 0;
}
还需要一个数据接收接口dataTransRcv
,读取共享内存的数据。此外为了防止数据处理速度大于发送速度,导致内存反复刷新,要增加一个数据判断,判断是否是新数据,这里在发送数据时应该在数据包中放入数据帧号。
/*
功能:数据传输
参数:
shmbuf_: [in] 共享内存的映射地址
srcbuf_: [in] 存放接收到的数据的缓冲区
dataSize_ : [in] 要传输的数据大小
semid_: [in] 信号量ID
*/
int dataTransRcv(char* shmbuf_, char* srcbuf_, int dataSize_ ,int semid_) {struct sembuf stbu = {0};stbu.sem_op = -1;stbu.sem_num = 0;stbu.sem_flg = 0;semop(semid_, &stbu, 1);static int lastID = 0;if(shmbuf_[0] > lastID) { // 判断是否接收新到数据,防止反复刷新内存printf("dataTransRcv: data is receiving\n");memcpy(srcbuf_, shmbuf_, dataSize_);lastID = shmbuf_[0];printf("dataTransRcv: receive new data(indx = %d)\n", lastID);}else printf("buf is not flush\n");stbu.sem_op = 1;semop(semid_, &stbu, 1);return 0;
}
最后提供一个资源回收的接口deinitTransRcv
,这里仅需要分离共享内存的映射即可:
/*
功能:资源回收
参数:
shmbuf_: [in] 共享内存的映射地址
*/
void deinitTransRcv(char* shmbuf_) {if(-1 == shmdt(shmbuf_)) printf("deinitTransRcv: share memory detach fail\n");else printf("deinitTransRcv: share memory detach success\n");
}
将我们的接口接入程序后,数据接收端变为:
void app() {char* databuf = malloc(PACKET_SIZE);char* imagebuf = malloc(PACKET_SIZE);/* interface */int shmid = -1, semid = -1;char* shmbuf = NULL;initTransRcv(PACKET_SIZE, &shmbuf, &shmid, &semid);/* over */while(1) {dataTransRcv(shmbuf, databuf, PACKET_SIZE, semid);dataProcess(databuf, imagebuf);imageDisplay(imagebuf);usleep(200*1000);}deinitTransRcv(shmbuf);
}
完整代码
信号量教学项目-仪器开发
该资源为一个软件框架,主要帮助理解信号量,此外可以在改框架上进行开发。
该资源内部提供了完备的日志输出,用于快速定位问题。
目录说明:
dataGet.c:A公司提供的设备源代码
myapp.c:小明开发的程序
get:由dataGet.c编译出的可执行文件
out:由myapp.c编译出的可执行文件
小结
这节课我们使用了一个案例去帮助大家理解信号量的使用以及如何使用信号量中实现共享内存的访问管理。
主要涉及了:
(1)共享内存操作
(2)信号量操作
通过信号量保证同一时间只有一个进程在操作共享内存。
下一节我们将学习:linux多线(进)程编程——(10)信号
信号与信号量不一样哦!
结束语
(暂时编不出来了哈哈,后面再补上吧)