【Linux 物联网网关主控系统-Linux主控部分(一)】
Linux 物联网网关主控系统-Linux主控部分(一)
- 一、环境配置(只是概述)
- 1.VMware Workstation Pro 安装过程
- 2.Samba 服务器安装与配置过程
- 二、链表
- 1、核心基础信息
- 2、核心链表操作梳理
- 3、完整典型代码实现
- 三、文件I/0
- 1、文件 IO 基础与项目应用场景
- 2、文件 IO 核心系统调用(无缓存)
- 四、串口操作
- 1、核心概念:串口设备文件标识
- 2、串口核心操作流程(必按此步骤)
- 3、串口专属关键配置(基于 termios 结构体)
- 4、串口配置专属函数(4 个核心,头文件termios.h/unistd.h)
- 5、示例代码
- 五、库与系统调用
- 1.两个核心概念
- 2.核心区别
一、环境配置(只是概述)
1.VMware Workstation Pro 安装过程
1.安装前准备:获取 VMware15.5 安装包,可通过官网(https://www.vmware.com/go/getworkstationwin)或指定资料包获取。
2.许可协议确认:启动安装向导,阅读并接受 VMWARE 最终用户许可协议,点击下一步。
3.自定义安装设置:选择安装位置(默认 C:\Program Files (x86)\VMware\VMware Workstation),可选择是否安装增强型键盘驱动,完成后下一步;接着进行用户体验设置,选择是否启动时检查更新、是否加入客户体验提升计划,点击下一步。
4.快捷方式与安装:选择在桌面、开始菜单创建快捷方式,确认后开始安装,等待虚拟网络驱动等组件安装完成。
5.安装完成与激活:安装向导完成后,点击 “完成”,可选择立即输入许可证密钥(提供密钥如 UG5J2-0ME12-M89WY-NPWXX-WQH88 等),也可选择试用 30 天。
2.Samba 服务器安装与配置过程
(一)Ubuntu 侧安装与配置
1.安装 Samba 组件:执行命令sudo apt-get install samba samba-common,自 动安装相关依赖包。
2.创建共享目录并授权
-新建目录:mkdir work
-赋予最高权限:sudo chmod 777 work
-可通过cd work/和pwd验证目录路径(默认 /home/peng/work)。
3.创建 Samba 用户(解决创建失败问题)
-初始创建命令:sudo smbpasswd -a yikou(设置密码为 1),若失败需先创建-系统账号和组。
-失败补救:sudo groupadd yikou -g6000(创建组)→sudo useradd yikou -u 6000 -g 6000 -s /sbin/nologin -d /dev/null(创建系统用户)→再次执行sudo smbpasswd -a yikou设置密码。
4.修改 Samba 配置文件
打开配置文件:sudo vim /etc/samba/smb.conf
在文件尾部添加配置:
[yikou]path=/home/peng/work available=yes browseable=yes writable=yes5.重启 Samba 服务:执行/etc/init.d/smbd restart,提示 “ok” 即表示服务重启成功。
(二)Windows 侧映射网络驱动器
1.验证网络互通:在 Windows cmd 中执行ping Ubuntu的IP(示例 192.168.0.112),确保能正常连通。
2.映射网络驱动器
-打开 “此电脑”,点击 “映射网络驱动器”,选择驱动器号(示例 Y)。
-输入文件夹路径:\192.168.0.112\yikou,点击完成。
3.输入凭据访问:在弹出的 Windows 安全中心中,输入 Samba 用户名(yikou)和密码(1),即可访问 Ubuntu 的共享目录,实现 Linux 与 Windows 目录内容同步。
二、链表
1、核心基础信息
1. 链表应用背景
Linux 内核大量使用链表(如 list_head),用于设备 / 驱动 / 内存管理、等待队加粗样式列等;
本项目使用单链表,共 2 处应用(boa 进程、主控进程),核心操作:插入节点、读取 / 提取节点。
2. 自定义数据结构
链表的节点数据和节点本体通过typedef定义,是所有操作的基础,数据域存储消息类型和消息文本:
// 链表节点的数据类型(存储消息包)typedefstructmsg_pack{charmsg_type;// 消息类型,1字节chartext[27];// 消息文本,27字节}link_datatype;// 链表节点结构体定义typedefstruct_node_{link_datatype data;// 节点数据域struct_node_*next;// 节点指针域,指向下一个节点}linknode,*linklist;// linknode:节点类型 linklist:节点指针类型(链表句柄)3. 全局链表头 / 尾指针
用于标记单链表的头部和尾部,方便尾插法插入节点(项目核心插入方式):
linklist envlinkHead,envlinkTail;// 全局变量:链表头指针、链表尾指针2、核心链表操作梳理
项目实现单链表4 个核心操作,均基于尾插法实现插入,提取节点为头节点后第一个节点(头删法),操作逻辑简洁适配项目实际需求,各操作的功能、思路如下:
| 操作名称 | 函数原型 | 核心功能 | 关键思路 |
|---|---|---|---|
| 创建空链表 | linklist CreateEmptyLinklist() | 初始化头节点,设置全局头/尾指针,返回链表头 | 申请头节点内存→头节点 next 置 NULL→全局头/尾指针指向头节点 |
| 插入节点(尾插) | int InsertLinknode(link_datatype x) | 向链表尾部插入新节点,传入待存储的消息数据 | 申请新节点内存→尾指针 next 指向新节点→更新尾指针为新节点→赋值数据 + next 置 NULL |
| 遍历所有节点 | void show_list(linklist h) | 遍历整个链表,打印所有节点的消息文本 | 从头部 next 开始遍历→循环打印节点数据→指针后移至 NULL 结束 |
| 提取节点(头删) | linklist GetLinknode(linklist h) | 提取头节点后第一个有效节点,从链表中移除,返回该节点(空链表返回 NULL) | 判断链表是否为空→指向待提取节点→头节点 next 指向提取节点的下一个→更新尾指针(若提取最后一个节点) |
| 链表判空(辅助) | int Emptylinklist(linklist h) | 判断链表是否为空,空返回 1,非空返回 0 | 直接判断头节点 next 是否为 NULL |
3、完整典型代码实现
- 头文件:link_list.h(声明结构体、函数、全局变量)
#ifndefLINK_LIST_H#defineLINK_LIST_H#include<stdio.h>#include<stdlib.h>#include<string.h>// 链表节点的数据类型(消息包)typedefstructmsg_pack{charmsg_type;chartext[27];}link_datatype;// 链表节点结构体typedefstruct_node_{link_datatype data;struct_node_*next;}linknode,*linklist;// 全局链表头、尾指针externlinklist envlinkHead,envlinkTail;// 链表核心操作函数声明linklistCreateEmptyLinklist();// 创建空链表intInsertLinknode(link_datatype x);// 尾插节点voidshow_list(linklist h);// 遍历链表intEmptylinklist(linklist h);// 判空(辅助)linklistGetLinknode(linklist h);// 提取节点#endif- 实现文件:link_list.c(实现所有链表操作)
#include"link_list.h"// 定义全局链表头、尾指针linklist envlinkHead,envlinkTail;// 创建空链表:初始化头节点,设置全局头/尾指针linklistCreateEmptyLinklist(){linklist h=(linklist)malloc(sizeof(linknode));if(h==NULL){printf("Create head node error!\n");returnNULL;}h->next=NULL;// 头节点无有效数据,next置NULLenvlinkHead=h;// 全局头指针指向头节点envlinkTail=h;// 全局尾指针初始也指向头节点returnh;}// 链表判空:空返回1,非空返回0intEmptylinklist(linklist h){return(NULL==h->next)?1:0;}// 尾插节点:成功返回0,失败返回-1intInsertLinknode(link_datatype x){// 申请新节点内存linklist q=(linklist)malloc(sizeof(linknode));if(NULL==q){printf("InsertLinknode Error\n");return-1;}// 尾指针的next指向新节点,更新尾指针envlinkTail->next=q;envlinkTail=q;// 给新节点赋值,next置NULL(尾部节点)q->data=x;q->next=NULL;return0;}// 遍历链表,打印所有节点数据voidshow_list(linklist h){if(Emptylinklist(h)){printf("Linklist is empty!\n");return;}inti=0;linklist p=h->next;// 从头节点后第一个有效节点开始while(p!=NULL){printf("node [%d]: msg_type=0x%x, text=%s\n",i,p->data.msg_type,p->data.text);i++;p=p->next;// 指针后移}}// 提取头节点后第一个有效节点,返回该节点(空链表返回NULL)linklistGetLinknode(linklist h){// 链表为空,直接返回NULLif(1==Emptylinklist(h)){returnNULL;}linklist p=h->next;// 指向待提取的节点h->next=p->next;// 头节点next指向提取节点的下一个,从链表移除该节点// 若提取的是最后一个节点,更新尾指针为头节点if(p->next==NULL){envlinkTail=h;}returnp;// 返回提取的节点}- 测试文件:main.c(模拟项目使用,验证所有操作)
#include"link_list.h"intmain(){// 1. 创建空链表linklist head=CreateEmptyLinklist();if(head==NULL){return-1;}printf("===== 创建空链表完成 =====\n");// 2. 定义并赋值3个消息数据,插入链表link_datatype data1,data2,data3;data1.msg_type=0x1;memcpy(data1.text,"1111",sizeof(data1.text));InsertLinknode(data1);data2.msg_type=0x2;memcpy(data2.text,"2222",sizeof(data2.text));InsertLinknode(data2);data3.msg_type=0x3;memcpy(data3.text,"nnnn",sizeof(data3.text));InsertLinknode(data3);printf("===== 3个节点插入完成,遍历链表 =====\n");show_list(head);// 3. 提取节点并释放内存,遍历验证linklist tmpnode=GetLinknode(head);if(tmpnode!=NULL){printf("\n===== 提取节点:text=%s =====\n",tmpnode->data.text);free(tmpnode);// 释放提取的节点内存,避免内存泄漏}printf("===== 提取节点后,遍历链表 =====\n");show_list(head);// 4. 继续提取所有节点,验证空链表tmpnode=GetLinknode(head);free(tmpnode);tmpnode=GetLinknode(head);free(tmpnode);printf("\n===== 提取所有节点后,遍历链表 =====\n");show_list(head);return0;}4.使用测试
gcc main.c link_list.c-o run./run=====创建空链表完成==========3个节点插入完成,遍历链表=====node[0]:msg_type=0x1,text=1111node[1]:msg_type=0x2,text=2222node[2]:msg_type=0x3,text=nnnn=====提取节点:text=1111==========提取节点后,遍历链表=====node[0]:msg_type=0x2,text=2222node[1]:msg_type=0x3,text=nnnn=====提取所有节点后,遍历链表=====Linklist is empty!三、文件I/0
1、文件 IO 基础与项目应用场景
1.核心定义:Linux 遵循一切皆文件,文件 IO 即文件的输入输出,通过内核提供的系统调用实现对各类文件的读写操作,是无缓存的底层 IO 操作。
2.项目应用:协调器通过串口与 Ubuntu 上位机通信,串口被模拟为字符设备文件(/dev/ttyUSB0/1、/dev/ttyS0/1),应用程序直接通过open/read/write/close等系统调用操作这些设备文件,实现数据交互。
3.Linux 文件类型:共 8 类,核心关注与项目相关的字符设备文件(c)(串口、键盘等),此外还包括普通文件(-)、目录文件(d)、块设备文件(b)、管道文件(p)、套接字(s)、硬 / 软链接等,每种文件有专属标识和特性。
2、文件 IO 核心系统调用(无缓存)
| 函数 | 头文件 | 核心功能 | 关键参数 / 特性 |
|---|---|---|---|
| open() | sys/types.h、sys/stat.h、fcntl.h | 打开 / 创建文件,返回文件描述符(最小未用非负整数) | 1. 必传:文件路径pathname、打开标志flags;2. 创建设备时加传:权限mode(8 进制);3. 常用flags:O_RDONLY/WRONLY/RDWR(读写模式,互斥)、O_CREAT(创建文件)、O_APPEND(追加写入)、O_TRUNC(清空原有数据);4. 不可创建设备文件,需用mknod() |
| close() | unistd.h | 关闭已打开的文件描述符 | 1. 传参:文件描述符fd;2. 进程终止时内核自动关闭其打开的所有文件;3. 关闭后释放文件上的记录锁 |
| read() | unistd.h | 从可读文件 / 设备读取数据到缓冲区 | 1. 传参:fd、数据缓冲区buf、最大读取字节数count;2. 从文件当前位移量开始读取,成功后位移量增加实际读取字节数;3. 返回值:实际读取字节数 / 0(文件末尾)/-1(出错) |
| write() | unistd.h | 向可写文件 / 设备写入缓冲区数据 | 1. 传参:fd、待写数据缓冲区buf、待写字节数count;2. 带O_APPEND时,每次写操作前将位移量移至文件末尾;3. 返回值通常与count不同,需循环写入确保数据全部写入;4. 常见错误:磁盘满、超出文件长度限制 |
| lseek() | sys/types.h、unistd.h | 显式定位文件的当前位移量 | 1. 传参:fd、偏移量offset(可正可负)、基准点whence;2. 基准点:SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾);3. 特性:仅记录位移量,不触发实际 IO;仅对常规文件有效,对 socket / 管道 / FIFO 无效;位移量可大于文件长度,写操作会形成文件空洞 |
四、串口操作
1、核心概念:串口设备文件标识
Windows下
• COM3 …
Linux下
• ttyUSB0…
• ttyS0…
2、串口核心操作流程(必按此步骤)
打开串口设备文件(open)→ 配置串口参数(波特率/数据位/校验位等)→ 读写串口数据(read/write)→ 关闭串口(close)
3、串口专属关键配置(基于 termios 结构体)
所有串口通信参数(波特率、数据位、超时等)都通过struct termios结构体配置,头文件#include <asm-generic/termbits.h>(都是linux内核自带的),只需关注3 个核心成员,其余默认即可:
- c_cflag(控制模式,最核心):配置波特率、数据位、校验位、停止位
串口通信的基础参数,必配项,常用常量:
CLOCAL:忽略调制解调器控制线,必设,避免串口被其他进程占用;
CREAD:启用串口接收功能,必设,否则无法读取串口数据;
CS8:设置数据位为 8 位(最常用,还有 CS5/CS6/CS7),必设;
PARENB:启用奇偶校验(无需校验则不设);
PARODD:搭配 PARENB,设为奇校验(不设则为偶校验);
CSTOPB:设置 2 个停止位(不设则为 1 个,最常用)。 - c_cc [VTIME/VMIN](控制字符,配置读写超时 / 最小字节数)
open 函数未设置非阻塞模式时才生效,控制 read 读取串口数据的返回规则,串口通信必配,单位 / 含义固定:
VTIME:等待时间,单位百毫秒(0.1 秒),如 VTIME=5 代表等待 0.5 秒;
VMIN:要求读取的最小字节数,如 VMIN=24 代表至少读到 24 字节才返回。
最常用组合(项目解析环境数据用)
VMIN>0 + VTIME>0:读到VMIN 指定的字节数 或 VTIME 超时,满足其一就返回,适配固定长度的传感器数据读取。 - 波特率
不属于 termios 结构体成员,但需通过专属函数设置,输入 / 输出波特率必须一致(如协调器和 Ubuntu 都设为 9600),常用值:9600、115200。
4、串口配置专属函数(4 个核心,头文件termios.h/unistd.h)
| 函数 | 核心功能 | 关键参数 / 使用说明 |
|---|---|---|
| tcgetattr(int fd, struct termios *tp) | 获取串口当前配置 | fd:串口文件描述符;tp:termios 结构体指针,用于接收当前配置,配置前必调 |
| tcsetattr(int fd, int opt, const struct termios *tp) | 设置串口配置 | opt 设为 TCSANOW(修改立即生效,必选);配置完成后必调,让参数生效 |
| cfsetispeed(struct termios *tp, speed_t speed) | 设置串口输入波特率 | speed 设为 B9600/B115200(对应波特率 9600/115200) |
| cfsetospeed(struct termios *tp, speed_t speed) | 设置串口输出波特率 | 与输入波特率设为相同值,如 B9600 |
5、示例代码
#include<fcntl.h>#include<unistd.h>#include<termios.h>intmain(){intfd;structtermiosnewtio;// 1. 打开串口设备(只读,不阻塞)fd=open("/dev/ttyUSB0",O_RDWR);// 2. 获取当前串口配置tcgetattr(fd,&newtio);// 3. 配置核心参数:启用接收、忽略控制线、8位数据位newtio.c_cflag|=CLOCAL|CREAD|CS8;// 4. 配置读写规则:最小24字节,超时500毫秒newtio.c_cc[VMIN]=24;newtio.c_cc[VTIME]=5;// 5. 设置波特率为9600cfsetispeed(&newtio,B9600);cfsetospeed(&newtio,B9600);// 6. 使配置立即生效tcsetattr(fd,TCSANOW,&newtio);// 7. 读写串口数据(与普通文件IO一致)charbuf[256];read(fd,buf,256);// 读传感器数据write(fd,"cmd",3);// 写指令给协调器// 8. 关闭串口close(fd);return0;}五、库与系统调用
1.两个核心概念
系统调用(System Call)
系统调用是用户程序访问 Linux 内核的唯一合法入口,相当于用户空间和内核空间的 “官方桥梁”。
本质:内核提供的、运行在内核态的底层接口
作用:帮用户程序完成硬件操作、文件读写、进程管理等特权操作,用户程序不能直接操作硬件,必须通过系统调用
常用的:open()/read()/write()/close() 都是系统调用
库函数(Library Function)
库函数是C 语言标准库(glibc)封装好的 API 集合,运行在用户态,是给开发者用的 “便捷工具包”。
2.核心区别
| 分类 | 特点 | 例子 |
|---|---|---|
| 不涉及系统调用的库函数 | 纯用户态操作,不访问内核,只做数据计算/处理 | abs()(求绝对值)、strlen()(算字符串长度)、toupper()(转大写) |
| 涉及系统调用的库函数 | 底层封装了系统调用,最终还是要进入内核态 | fopen()(底层调用open())、fread()(底层调用read())、fwrite()(底层调用write())、pthread_create()(创建线程,底层调用系统调用) |
而涉及系统调用的库函数和系统调用的关系
应用程序(用户态) ↗️ 直接调用(系统调用) ↘️ 调用C库函数(用户态) → 再调用系统调用(内核态) 系统调用(内核态) → 操作系统内核