【Linux 物联网网关主控系统-Linux主控部分(三)】
Linux 物联网网关主控系统-Linux主控部分三一、进程间通信1.历史和分类2.IPC 对象核心通用特性System V IPC 专属3.IPC 对象的系统操作工具二、共享内存1.核心特点最核心的两个点2.项目实际应用场景3.共享内存四步使用流程固定步骤所有场景通用4.示例代码三、消息队列1、核心定位2.消息队列函数调用流程3.核心函数4.示例代码四、信号灯信号量1.核心概念2.核心函数3.示例代码五、信号1.核心概念2.信号核心函数总表3.常用信号4.示例代码六、总结进程间通讯方式比较一、进程间通信1.历史和分类Linux 的进程间通信机制源自 UNIX 两大经典派系的改进与扩充是对原有机制的全面继承早期 UNIX仅支持管道、有名管道、信号三种基础通信方式功能简单适用场景有限ATT 贝尔实验室基于早期方式改进形成System V IPC特点是通信进程仅限单个计算机内核心包含共享内存、消息队列、信号灯BSD加州大学伯克利分校创新实现基于套接字socket的通信机制支持计算机之间的跨主机进程通信突破了单主机限制。Linux 整合了上述所有 UNIX 通信方式按特性和溯源分为三大类也是实际开发中最常用的方式其中System V IPC是本项目的核心使用类型2.IPC 对象核心通用特性System V IPC 专属共享内存、消息队列、信号灯均为IPCInter-Process Communication对象拥有统一的核心操作逻辑和特性是区别于传统 IPC 的关键1.标识体系通过key_t类型的key 值唯一关联 IPC 对象key 值可由ftok()函数生成基于路径 字符也可使用IPC_PRIVATE私有键2.操作流程所有 IPC 对象遵循统一三步操作—— 通过 key 值打开 / 创建 IPC 通道→通过 IPC 对象 ID访问 / 操作通道→通过控制函数管理 / 删除 IPC 对象3.生命周期由内核维护创建后若不手动删除会一直保留在系统中直至系统重启因此使用后必须手动释放避免资源泄漏。✅ 只有共享内存、消息队列、信号灯 是 System V IPC 对象有这 3 个专属共性❌ 管道、有名管道、信号、socket 都不是没有这些共性各玩各的。其他 IPC 方式的核心区别没统一的 key/ID用文件描述符、路径等方式访问进程退出 / 关闭句柄内核会自动回收资源不用手动删各自操作逻辑完全不同没有统一规范。3.IPC 对象的系统操作工具Linux 提供专用命令用于查看和删除系统中的 IPC 对象是开发调试、资源释放的必备工具核心为ipcs和ipcrm二者配合使用ipcs查看系统中已存在的 IPC 对象通用使用直接输入ipcs查看所有类型 IPC 对象共享内存、消息队列、信号灯精准查看ipcs -m共享内存、ipcs -s信号灯、ipcs -q消息队列。ipcrm删除系统中指定的 IPC 对象核心用法ipcrm 类型参数 IPC对象ID如ipcrm -m 共享内存ID、ipcrm -q 消息队列ID、ipcrm -s 信号灯ID核心作用释放 IPC 对象占用的系统资源解决 “IPC 对象永久驻留内核” 的问题。二、共享内存1.核心特点最核心的两个点1.效率最高内核预留一块公共内存区进程直接将其映射到自己的地址空间读写无需数据拷贝直接操作内存速度远快于其他 IPC2.需同步配合多进程可同时访问这块内存无天然的互斥 / 同步机制必须搭配信号灯信号量 使用防止数据读写冲突。2.项目实际应用场景用于zigbee 网络数据的跨进程共享1.串口采集线程将 zigbee 的网络环境数据写入共享内存2.web/APP 对应的 boa 进程等其他进程定时从共享内存读取数据3.读写规则读数据不清除原有内容写数据直接覆盖更新保证数据实时性。3.共享内存四步使用流程固定步骤所有场景通用1.创建 / 打开通过 key 值创建或打开一块共享内存得到唯一的内存 ID2.映射将内核的共享内存映射到当前进程的私有地址空间进程才能直接读写3.撤销映射进程使用完后解除自身地址空间与共享内存的关联4.删除手动删除内核中的共享内存对象释放系统资源核心不删则永久驻留。对应函数核心函数 1shmget创建 / 打开共享内存核心函数 2shmat映射共享内存到进程地址空间核心函数 3shmdt撤销共享内存映射核心函数 4shmctl控制 / 删除共享内存4.示例代码✅ 正确顺序共享内存 / 消息队列 完全一样先运行 server创建共享内存 / 消息队列再运行 client访问已创建的对象shm-client.c 完整代码#includestdio.h#includestdlib.h#includestring.h#includesys/types.h#includesys/ipc.h#includesys/shm.h#includeunistd.h#includetime.h#defineSEG_SIZE1024// 共享内存段大小示例中未显式定义补充完整intmain(){key_tkey_info;char*mem_ptr;intseg_id;// 生成唯一键值if((key_infoftok(/app,i))0){perror(ftok info);exit(-1);}// 打开已存在的共享内存段seg_idshmget(key_info,SEG_SIZE,0777);if(seg_id-1){perror(shmget client);exit(-1);}// 映射共享内存到进程地址空间mem_ptrshmat(seg_id,NULL,0);if(mem_ptr(void*)-1){perror(shmat client);exit(-1);}// 读取共享内存中的数据并打印printf(The time, direct from memory: ..%s,mem_ptr);// 撤销共享内存映射shmdt(mem_ptr);return0;}shm-server.c 完整代码#includestdio.h#includestdlib.h#includestring.h#includesys/types.h#includesys/ipc.h#includesys/shm.h#includeunistd.h#includetime.h#defineSEG_SIZE1024// 共享内存段大小示例中未显式定义补充完整intmain(){longnow;intn;key_tkey_info;char*mem_ptr;intseg_id;// 生成唯一键值if((key_infoftok(/app,i))0){perror(ftok info);exit(-1);}// 创建共享内存段不存在则创建权限0777seg_idshmget(key_info,SEG_SIZE,IPC_CREAT|0777);if(seg_id-1){perror(shmget server);exit(-1);}// 映射共享内存到进程地址空间mem_ptrshmat(seg_id,NULL,0);if(mem_ptr(void*)-1){perror(shmat server);exit(-1);}// 循环63次每秒向共享内存写入当前时间for(n0;n63;n){time(now);// 获取当前系统时间strcpy(mem_ptr,ctime(now));// 将时间字符串写入共享内存sleep(1);// 等待1秒}// 删除共享内存段shmctl(seg_id,IPC_RMID,NULL);return0;}三、消息队列1、核心定位消息队列是 Linux 内核维护的链表式消息队列属于 System V IPC 的一种和共享内存、信号量并列。核心特点异步通信发送方把消息放进队列就可以走接收方按需从队列取消息不需要双方同时在线。核心逻辑和你刚才的共享内存示例一样也是服务端 客户端的配对模式通过ftok生成的唯一 key 访问同一个队列。消息队列在该项目中使用从web页面或者APP下发的命令最终要通过串口发送给zigbee网络的终端节点以控制各种外设需要考虑以下问题所有的命令不能丢失连续点击页面按钮命令需要按照一定顺序排列zigbee 网络可能延时相比较有点大不能发送太快2.消息队列函数调用流程发送端msgsnd 侧ftok生成唯一键值msgget创建 / 打开消息队列msgsnd按自定义消息结构体含type类型将消息写入内核队列msgctl(IPC_RMID)删除消息队列收尾接收端msgrcv 侧ftok用相同参数生成同一键值msgget打开已存在的消息队列msgrcv按指定type如type1从队列中筛选对应类型的消息读取msgctl(IPC_RMID)删除消息队列收尾3.核心函数函数名函数原型核心功能重点参数返回值msggetint msgget(key_t key, int msgflg);创建/打开消息队列key键值msgflg创建标志权限成功msqid失败-1msgsndint msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);发送消息msqid、消息结构体、数据长度成功0失败-1msgrcvssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);接收消息msgtyp消息类型成功长度失败-1msgctlint msgctl(int msqid, int cmd, struct msqid_ds *buf);控制/删除cmdIPC_RMID 删除队列成功0失败-14.示例代码msg-server.c#includestdio.h#includestdlib.h#includestring.h#includesys/types.h#includesys/ipc.h#includesys/msg.h#includeunistd.h#includetime.h#defineMSG_SIZE1024// 消息内容最大长度// 自定义消息结构体必须以long mtype开头structmsgbuf{longmtype;charmtext[MSG_SIZE];};intmain(){key_tkey_info;intmsq_id;structmsgbufmsg;// 1. 生成唯一键值和共享内存完全一致if((key_infoftok(/app,i))0){perror(ftok info);exit(-1);}// 2. 创建消息队列对应shmget加IPC_CREATmsq_idmsgget(key_info,IPC_CREAT|0777);if(msq_id-1){perror(msgget server);exit(-1);}// 3. 循环发送63条时间消息对应共享内存server的写操作for(intn0;n63;n){time_tnowtime(NULL);msg.mtype1;// 消息类型设为1客户端按类型1读取strcpy(msg.mtext,ctime(now));// 把时间写入消息内容// 发送消息到队列if(msgsnd(msq_id,msg,strlen(msg.mtext),0)-1){perror(msgsnd);exit(-1);}sleep(1);// 每秒发一条}// 4. 删除消息队列对应shmctl IPC_RMIDmsgctl(msq_id,IPC_RMID,NULL);return0;}msg-client.c#includestdio.h#includestdlib.h#includestring.h#includesys/types.h#includesys/ipc.h#includesys/msg.h#includeunistd.h#defineMSG_SIZE1024// 必须和服务端完全一致的消息结构体structmsgbuf{longmtype;charmtext[MSG_SIZE];};intmain(){key_tkey_info;intmsq_id;structmsgbufmsg;// 1. 生成相同的键值找到同一个队列if((key_infoftok(/app,i))0){perror(ftok info);exit(-1);}// 2. 打开已存在的消息队列不加IPC_CREAT对应共享内存client的shmgetmsq_idmsgget(key_info,0777);if(msq_id-1){perror(msgget client);exit(-1);}// 3. 读取消息对应共享内存的printf读操作// msgtyp1只读取类型为1的消息和服务端对应if(msgrcv(msq_id,msg,MSG_SIZE,1,0)-1){perror(msgrcv);exit(-1);}printf(The time, from message queue: %s,msg.mtext);// 消息队列不需要解绑操作读取后直接退出即可return0;}四、信号灯信号量1.核心概念本项目中的作用共享内存需要互斥访问有进程在访问时其他进程不能访问信号量初始值为 12.核心函数函数名函数原型核心功能重点参数说明返回值semgetint semget(key_t key, int nsems, int semflg);创建 / 打开信号灯集keyftok 生成键值nsems信号灯集内信号灯数目PPT 中设 1semflgIPC_CREAT|0666权限成功信号灯集 IDsemid失败-1semctlint semctl(int semid, int semnum, int cmd, …/union semun arg/);控制信号灯初始化 / 获取值 / 删除semid信号灯集 IDsemnum信号灯编号从 0 开始cmdSETVAL设值/GETVAL取值/IPC_RMID删除集argunion semun 类型初始化值用成功0SETVAL/IPC_RMID/ 信号灯值GETVAL失败-1semopint semop(int semid, struct sembuf *opsptr, size_t nops);执行 PV 操作核心semid信号灯集 IDopsptrstruct sembuf 结构体指针定义 P/V 操作nops操作的信号灯个数PPT 中设 1成功0失败-13.示例代码头文件 sem_comm.hPV 操作封装#ifndefSEM_COMM_H#defineSEM_COMM_H#includestdio.h#includestdlib.h#includesys/types.h#includesys/ipc.h#includesys/sem.h#includesys/shm.h#includeunistd.h#defineSEM_INIT_VAL1// PPT信号量初始值1// 信号量必须的联合体unionsemun{intval;structsemid_ds*buf;unsignedshort*array;};// 初始化信号量intinit_sem(intsemid,intsemnum,intval){unionsemun un;un.valval;semctl(semid,semnum,SETVAL,un);return0;}// P操作-1加锁intsem_p(intsemid){structsembufbuf{0,-1,SEM_UNDO};semop(semid,buf,1);return0;}// V操作1解锁intsem_v(intsemid){structsembufbuf{0,1,SEM_UNDO};semop(semid,buf,1);return0;}// 删除信号量intdel_sem(intsemid){semctl(semid,0,IPC_RMID);return0;}#endif服务端 sem_server.c创建 写共享内存#includesem_comm.h#defineSHM_SIZE1024intmain(){key_tkeyftok(.,s);intsemidsemget(key,1,IPC_CREAT|0666);intshmidshmget(key,SHM_SIZE,IPC_CREAT|0666);char*shmaddrshmat(shmid,NULL,0);init_sem(semid,0,SEM_INIT_VAL);// 互斥访问共享内存sem_p(semid);printf(服务端正在写入共享内存...\n);sprintf(shmaddr,IPC TEST BY SERVER);sleep(3);printf(服务端写入完成\n);sem_v(semid);sleep(5);shmdt(shmaddr);shmctl(shmid,IPC_RMID,NULL);del_sem(semid);return0;}客户端 sem_client.c读取共享内存#includesem_comm.h#defineSHM_SIZE1024intmain(){key_tkeyftok(.,s);intsemidsemget(key,1,0666);intshmidshmget(key,SHM_SIZE,0666);char*shmaddrshmat(shmid,NULL,0);// 互斥读取sem_p(semid);printf(客户端读取数据%s\n,shmaddr);sem_v(semid);shmdt(shmaddr);return0;}五、信号1.核心概念信号是软件中断用于进程间通知、异常处理简单、携带信息量少不能传输大量数据每个信号有 默认处理方式忽略、终止、暂停、继续进程可以 自定义信号处理函数捕捉信号当我们按下 ctrlc 终止主控程序时希望能够实现如下操作进程退出释放所有申请的资源互斥锁、条件变量、消息队列、共享内存、信号量、线程、设备文件描述符摄像头、串口2.信号核心函数总表函数名函数原型核心功能重点参数说明返回值signalsighandler_t signal(int signum, sighandler_t handler);注册信号处理方式signum信号编号handlerSIG_IGN忽略/SIG_DFL默认/ 自定义函数成功旧处理函数地址失败SIG_ERRkillint kill(pid_t pid, int sig);向指定进程发送信号pid目标进程 IDsig要发送的信号成功0失败-1raiseint raise(int sig);向自己发送信号sig信号编号成功0失败-1pauseint pause(void);进程阻塞等待信号无参数永远返回 -1sleepunsigned int sleep(unsigned int seconds);休眠指定秒数被信号唤醒会提前返回seconds秒数剩余未休眠秒数3.常用信号SIGINT 2 终端中断CtrlCSIGQUIT 3 退出Ctrl\SIGKILL 9 强制杀死进程不能捕捉、不能忽略SIGUSR1 10 用户自定义信号 1SIGUSR2 12 用户自定义信号 2SIGALRM 14 闹钟信号SIGCHLD 17 子进程退出时发给父进程4.示例代码signal 程序负责等着收信号被 CtrlC 或 kill 通知kill 程序负责主动发信号给别人signal 标准示例代码#includestdio.h#includesignal.h#includeunistd.h// 自定义信号处理函数voidhandle(intsig){printf(收到信号%d我不会退出\n,sig);}intmain(){// 注册 SIGINT(CtrlC) 的处理方式signal(SIGINT,handle);printf(等待信号...按 CtrlC 测试\n);while(1){pause();// 等待信号}return0;}kill 发送信号示例代码#includestdio.h#includesignal.h#includestdlib.hintmain(intargc,char*argv[]){if(argc!3){printf(用法%s PID 信号编号\n,argv[0]);return-1;}intpidatoi(argv[1]);intsigatoi(argv[2]);kill(pid,sig);printf(已发送信号 %d 给进程 %d\n,sig,pid);return0;}编译运行gcc signal.c-osignal ./signal# 另开终端ps-ef|grepsignalkill-2进程号六、总结进程间通讯方式比较signal: 唯一的异步通信方式msg: 常用于 cs 模式中按消息类型访问可有优先级shm: 效率最高 (直接访问内存)需要同步、互斥机制sem: 配合共享内存使用用以实现同步和互斥pipe: 具有亲缘关系的进程间单工数据在内存中fifo: 可用于任意进程间双工有文件名数据在内存