linux用户态与内核态通过字符设备交互

linux用户态与内核态通过字符设备交互

简述

Linux设备分为三类,字符设备、块设备、网络接口设备。字符设备只能一个字节一个字节读取,常见外设基本都是字符设备。块设备一般用于存储设备,一块一块的读取。网络设备,Linux将对网络通信抽象成一个设备,通过套接字对其进行操作。

在这里插入图片描述

对于字符设备的用户态与内核态交互,主要涉及到打开、读取、写入、关闭等操作。通过字符设备实现内核与用户程序的交互,设计实现一个内核态监控文件目录及文件复制拷贝的内核模块程序,其中字符设备交互时序图如下:

user_space kernel_space 发送监控目录信息(list) 回复监控目录信息已设置 user_space kernel_space

通信协议格式

[2bytes数据长度] + |2bytes目录路径数量| + |2bytes 长度| + |目录数据| + ... + |2bytes 长度| + |目录数据|

控制命令定义

#include <linux/ioctl.h>#define BASEMINOR 0
#define COUNT 5
#define NAME "ioctl_test"#define IOCTL_TYPE 'k'//定义无参的命令
#define IOCTL_NO_ARG _IO(IOCTL_TYPE, 1)//用户空间向内核空间写
#define IOCTL_WRITE_INT _IOW(IOCTL_TYPE, 2,int)//用户空间从内核空间读
#define IOCTL_READ_INT _IOR(IOCTL_TYPE, 3, int)//用户空间向内核空间写
#define IOCTL_WRITE_STRING _IOW(IOCTL_TYPE, 4,char*)//用户空间从内核空间读
#define IOCTL_READ_STRING _IOR(IOCTL_TYPE, 5, char*)#define IOCTL_MAXNR 5

上述命令实现了用户态向内核态写入、读取int型或string类型的数据,定义控制命令个数为5

用户态程序

#include <stdio.h>
#include <string.h>
#include <linux/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include "cmd.h"
enum arg_type{ARG_INT,ARG_STRING
};
union data{int integer;char string[255];};struct arg_node{int type; //字符串类型union data arg_data;struct arg_node*next;
};
void insert_node(struct arg_node**head, struct arg_node * item ){if(item==NULL){printf("待插入节点指针为空\n");return ;}if(*head == NULL){*head = item;printf("节点指针赋值,%p\n",*head);}else{struct arg_node *current = *head;while(current->next != NULL){current = current->next;}current->next = item;}}//参数格式:user_ipc -int 200 -string "12324154"
int main(int argc, char *argv[])
{if(argc<2 || argc%2==0){printf("参数个数不匹配\n");return -1;}int fd = 0;int arg = 0;fd = open("/dev/ioctl_test", O_RDWR);if(fd < 0){printf("open memdev0 failed!\n");return -1;}if(ioctl(fd, IOCTL_NO_ARG, &arg) < 0){printf("----打印命令传输失败----\n");return -1;}unsigned char *protocol_body = NULL;int init_length = 5;int realloc_length = 10;int len_tag_bytes = 2;protocol_body = calloc(init_length, sizeof(unsigned char )*init_length);int index = 4;int num_of_dirs = 0;struct arg_node *p_head = NULL;int i=0;for(i=1; i<argc; i=i+2){if(strcmp(argv[i],"-int") == 0){struct arg_node*p_item = malloc(sizeof(struct arg_node));p_item->next = NULL;p_item->type = ARG_INT;p_item->arg_data.integer = atoi(argv[i+1]);insert_node(&p_head, p_item);printf("插入int类型,值: %d \n",p_item->arg_data.integer);if(p_head==NULL)printf("链表头指针为空\n");}else if(strcmp(argv[i], "-string") == 0){struct arg_node *p_item = malloc(sizeof(struct arg_node));p_item->next = NULL;		p_item->type = ARG_STRING;memcpy(p_item->arg_data.string, argv[i+1],strlen(argv[i+1]));insert_node(&p_head, p_item);printf("插入string类型,值: %s \n",p_item->arg_data.string);//插入值组装协议数据包[2bytes数据长度] + [2bytes 字符串数量] +[2bytes长度] + [目录绝对路径] ... + [2bytes长度] + [目录绝对路径]int length = strlen(argv[i+1]);if((index+len_tag_bytes+length) > init_length) //空间不够,再分配{realloc_length = length + len_tag_bytes + 1; //计算再分配字节,多分配1个字节,作为结束nullprotocol_body = realloc(protocol_body, sizeof(unsigned char)*(init_length + realloc_length));if(!protocol_body){printf("再分配空间失败\n");exit(-1);}memset(protocol_body+index, 0, sizeof(unsigned char)*(init_length + realloc_length)); //初始化再分配空间为零init_length += realloc_length;printf("新分配空间成功,新分配空间字节大小 %d,总空间大小 %d\n",realloc_length, init_length);}protocol_body[index] = length / 256 ;protocol_body[index + 1] = length % 256;index = index + 2;memcpy(protocol_body + index, argv[i+1],length);index = index + length;num_of_dirs++;}}index = index -2;protocol_body[0] = index / 256;protocol_body[1] = index % 256;protocol_body[2] = num_of_dirs /256;protocol_body[3] = num_of_dirs %256;printf("组包数据:%d\n",index);for(i=0; i<index+2; i++){printf("%02x ",protocol_body[i]);}printf("\n");//内核交互 -- 字符设备if(ioctl(fd, IOCTL_WRITE_STRING, protocol_body)<0){printf("----用户态向内核写入字符串数据失败----\n");return -1;}char recv[256]={0};if(ioctl(fd,IOCTL_READ_STRING,recv)<0){printf("----用户态从内核态读取字符串数据失败----\n");return -1;}printf("从内核态读取数据:%s\n",recv);//释放申请内存free(protocol_body);protocol_body = NULL;close(fd);return 0;
}

上述代码实现把多个int或者char*类型的数据插入链表中,但是实际使用中,这个链表比没有用,和用户态交互,我只使用了string类型的数据,再把数据存入到protocol_body中,通过控制命令IOCTL_WRITE_STRING,实现把protocol_body写入到字符设备,供内核模块读取,同时内核模块返回一个随机数。

编译命令

gcc -o user_ipc user_ipc.c

内核模块

//msg_recv_send.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/unistd.h>
#include <linux/random.h>
#include "cmd.h"
#include "ctl_data.h"dev_t dev_num;
struct cdev *cdevp = NULL;/*
struct dir_node{int length; //长度char *dir_s; //目录字符串struct list_head list; //链表
};
*/
LIST_HEAD(msg_list_head);//处理
int handle_recv_msg(char *msg, int len){int ret = 0;int dir_index=0;//清空链表struct dir_node *entry, *tmp;list_for_each_entry_safe(entry, tmp, &msg_list_head,list){list_del(&entry->list);kfree(entry->dir_s);kfree(entry);}//解析数据int dir_length = 0;int num_of_dirs = 0;int char_index = 2;num_of_dirs = msg[0]<<8 | msg[1];for(dir_index=0; dir_index<num_of_dirs; dir_index++){dir_length = msg[char_index]<<8 | msg[char_index+1];char_index = char_index + 2;struct dir_node * new_node = kmalloc(sizeof(struct dir_node),GFP_KERNEL);new_node->dir_s = kmalloc(sizeof(char)*(dir_length+1),GFP_KERNEL);memset(new_node->dir_s, 0, dir_length+1);new_node->length = dir_length;INIT_LIST_HEAD(&new_node->list);memcpy(new_node->dir_s, msg+char_index, dir_length);char_index = char_index + dir_length;list_add_tail(&new_node->list, &msg_list_head);}//遍历列表list_for_each_entry(entry, &msg_list_head, list){printk(KERN_INFO "接收数据:%s\n",entry->dir_s);}	return ret;
}
static long my_ioctl(struct file * filp, unsigned int cmd, unsigned long arg){long ret = 0;int err = 0;int ioarg = 0;char kernel_buffer[256];unsigned int random_value = 0;		if(_IOC_TYPE(cmd) != IOCTL_TYPE){return -EINVAL;}if(_IOC_NR(cmd) > IOCTL_MAXNR){return -EINVAL;}if(_IOC_DIR(cmd) & _IOC_READ){err = !access_ok((void*)arg, _IOC_SIZE(cmd));}else if(_IOC_DIR(cmd) & _IOC_WRITE){err = !access_ok((void*)arg, _IOC_SIZE(cmd));}if(err){return -EFAULT;}switch(cmd){case IOCTL_NO_ARG:printk(KERN_INFO "print not arg cmd\n");break;case IOCTL_WRITE_INT:ret = __get_user(ioarg, (int*)arg);printk(KERN_INFO "get data from user space is :%d\n", ioarg);break;case IOCTL_READ_INT:ioarg = 1101;ret = __put_user(ioarg, (int *)arg);break;case IOCTL_WRITE_STRING:memset(kernel_buffer, 0, sizeof(kernel_buffer));unsigned char len[3]={0};ret = copy_from_user(len, (char*)arg, 2);int recv_len = 0;recv_len = len[0]*256 + len[1];printk(KERN_INFO "用户态写入的数据长度 %d",len[0]*256+len[1]);char *recv_buffer = kmalloc(sizeof(char)*recv_len,GFP_KERNEL);ret = copy_from_user(recv_buffer, (unsigned char*)(arg+2), recv_len);if(ret!=0){printk(KERN_INFO "从用户态拷贝数据失败,失败字节数 %d\n",ret);}printk(KERN_INFO "get data from user space is :%*ph\n",recv_len, recv_buffer);//处理接收到的字符串handle_recv_msg(recv_buffer, recv_len);kfree(recv_buffer);break;case IOCTL_READ_STRING://memset(random_value, 0, sizeof(random_value));memset(kernel_buffer, 0, sizeof(kernel_buffer));random_value = get_random_int();snprintf(kernel_buffer, sizeof(kernel_buffer),"返回随机字符串数值:%u",random_value);printk(KERN_INFO "kern_buffer : %s\n",kernel_buffer);ret = copy_to_user((char *)arg,kernel_buffer,sizeof(kernel_buffer));if(ret == 0){printk(KERN_INFO "写文本字符到用户态成功,[%s]。\n",(char*)arg);}else{printk(KERN_INFO "写文本字符到用户态失败,未写入字节数 %d。\n",ret);}break;default:return -EINVAL;}return ret;
}static const struct file_operations fops = {.owner = THIS_MODULE,.unlocked_ioctl = my_ioctl
};int __init ioctl_init(void ){int ret ;ret = alloc_chrdev_region(&dev_num, BASEMINOR, COUNT, NAME);if(ret < 0){printk(KERN_ERR "alloc_chrdev_region failed...\n");goto err1;}printk(KERN_INFO, "major = %d\n",MAJOR(dev_num));cdevp = cdev_alloc();if(NULL == cdevp){printk(KERN_ERR "cdev_alloc failed...\n");ret = -ENOMEM;goto err2;}cdev_init(cdevp, &fops);ret = cdev_add(cdevp, dev_num, COUNT);if(ret < 0){printk(KERN_INFO "cdev_add failed...\n");goto err2;}printk(KERN_INFO "------init completely\n");return 0;
err2:unregister_chrdev_region(dev_num, COUNT);
err1:return ret;
}void __exit ioctl_exit(void){cdev_del(cdevp);unregister_chrdev_region(dev_num, COUNT);printk(KERN_INFO "exit success.");
}

上述代码中alloc_chrdev_region分配一个名为NAME的字符设备,

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
//dev 字符设备存储的指针,高12位是主设备号,低20位是从设备号
//baseminor是从设备号
//count 请求的设备号数量
//name  设备名

内核模块主文件

//file_shield.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/unistd.h>
#include <asm/ptrace.h>
#include <linux/kallsyms.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/cred.h>
#include "hook_func.h"
#include "ctl_data.h"
#include "msg_recv_send.h"MODULE_LICENSE("GPL");//增加字符设备处理逻辑代码static int __init file_shield_init(void){int ret = 0;printk(KERN_INFO "init completly");//创建字符设备ioctl_init();printk(KERN_INFO "模块已加载\n");return ret;
}
static void __exit file_shield_exit(void){//卸载字符设备ioctl_exit();printk(KERN_INFO "模块已卸载\n");
}
module_init(file_shield_init);
module_exit(file_shield_exit);

内核模块Makefile

KERNELDIR:=/lib/modules/$(shell uname -r)/build
EXTRA_CFLAGS +=-O1PWD = $(shell pwd)obj-m +=file_hook.o
file_hook-objs:=file_shield.o msg_recv_send.o all:make -C $(KERNELDIR) M=$(PWD) modules
clean:make -C $(KERNELDIR) M=$(PWD) clean

编译

sudo make

输出

 LD [M]  /home/admin01/file-shield/file_hook.oBuilding modules, stage 2.MODPOST 1 modulesCC [M]  /home/admin01/file-shield/file_hook.mod.oLD [M]  /home/admin01/file-shield/file_hook.ko
make[1]: 离开目录“/usr/src/linux-headers-5.4.18-53-generic”

设备节点文件

在Linux系统中,设备节点文件是一种用于与设备进行交互的接口。这些设备节点文件通常位于/dev目录下。在Linux系统中,设备节点文件是一种用于与设备进行交互的接口。这些设备节点文件通常位于/dev目录下。

设备节点文件是Linux中的一种特殊文件,用于与设备进行通信。它们允许用户空间程序通过标准的文件I/O操作(如打开、读取、写入、关闭)来与设备进行交互。在/dev目录下的每个设备节点文件都对应一个特定的设备或设备类。

在内核模块中注册字符设备时,通常使用cdev_add函数,它会告诉内核创建相应的设备节点文件。这些设备节点文件将在/dev目录下动态创建,以便用户空间程序能够访问注册的设备。

例如,如果你的设备被命名为my_device,在/dev目录下将创建一个名为my_device的设备节点文件。用户空间程序可以通过打开/dev/my_device来访问你的设备。

需要手动创建一个设备节点文件

sudo mknod /dev/ioctl_test c 240 0

加载内核模块

sudo insmod path/file_hook.ko

卸载内核模块

sudo rmmod file_hook

测试

用户态程序发送接收
在这里插入图片描述

内核模块发送与接收
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/231870.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

异常控制流ECF

大家好&#xff0c;我叫徐锦桐&#xff0c;个人博客地址为www.xujintong.com&#xff0c;github地址为https://github.com/jintongxu。平时记录一下学习计算机过程中获取的知识&#xff0c;还有日常折腾的经验&#xff0c;欢迎大家访问。 一、异常控制流&#xff08;ECF) 现代…

【年终总结系列 2023】新起点,同时追寻更高的起点

什么是攀登者&#xff0c;用一个场景来概括就是&#xff1a;经常弯腰低头手脚并用向上攀爬&#xff0c;待到山的顶峰后终于可以舒展一下身体&#xff0c;但若舒展的时间过长便会觉得无聊&#xff0c;此时向远处眺望&#xff0c;发现了更高的山峰&#xff0c;便又充满了激情。对…

web前端——clear可以清除浮动产生的影响

clear可以解决高度塌陷的问题&#xff0c;产生的副作用要小 未使用clear之前 <!DOCTYPE html> <head><meta charset"UTF-8"><title>高度塌陷相关学习</title><style>div{font-size:50px;}.box1{width:200px;height:200px;backg…

排序算法——关于快速排序的详解

目录 1.基本思想 2.基本原理 2.1划分思想 2.2排序过程 &#xff08;1&#xff09;选择基准值 &#xff08;2&#xff09;分割过程&#xff08;Partition&#xff09; &#xff08;3&#xff09;递归排序 &#xff08;4&#xff09;合并过程 2.3具体实例 2.4实现代码 2.5关键要…

软件工程:用例图相关知识和多实例分析

目录 一、用例图相关知识 1. 基本介绍 2. 常用符号 二、用例图实例分析 1. 新闻管理系统 2. 医院病房监护系统 3. 实验上机安排系统 4. 远程网络教学系统 一、用例图相关知识 1. 基本介绍 用例图&#xff08;use case diagram&#xff09;是用户与系统交互的最简表示…

HarmonyOS 组件通用属性之位置设置

本文 我们来说 通用属性中的位置设置 主要是针对组件的对齐方式 布局方向 显示位置 做过WEB开发的 对流式布局应该都不陌生 就是 一行放内容 不够放就换行 我们可以先这样写 Entry Component struct Index {build() {Row() {Column() {Stack(){Text("你好")Text(&…

Springboot整合MQ学习记录

Mq介绍 RabbitMQ是由erlang语言开发&#xff0c;基于AMQP&#xff08;Advanced Message Queue 高级消息队列协议&#xff09;协议实现的消息队列&#xff0c;它是一种应用程序之间的通信方法&#xff0c;消息队列在分布式系统开发中应用非常广泛。支持Windows、Linux/Unix、MA…

<HarmonyOS主题课>1~3课后习题汇总

&#xff1c;HarmonyOS第一课&#xff1e;1~10课后习题汇总 1使用DevEco Studio高效开发 单选题 用哪一种装饰器修饰的组件可作为页面入口组件&#xff1f;&#xff08;B&#xff09; A. ComponentB. EntryC. PreviewD. Builder ArkTS Stage模型支持API Version 9&#xf…

智能座舱的下一个价值“爆点”——让“光”更智能

汽车智能化快速升级&#xff0c;智能座舱作为人机交互的主要窗口&#xff0c;交互模态、用户体验也呈现多维度升级。 例如&#xff0c;今年下半年上市的多款高端智能车型纷纷基于高性能座舱硬件平台&#xff0c;集成了AR-HUD、DMS/OMS等高阶功能&#xff0c;同时结合超大屏/多…

简单介绍Java 的内存泄漏

java最明显的一个优势就是它的内存管理机制。你只需简单创建对象&#xff0c;java的垃圾回收机制负责分配和释放内存。然而情况并不像想像的那么简单&#xff0c;因为在Java应用中经常发生内存泄漏。 本教程演示了什么是内存泄漏&#xff0c;为什么会发生内存泄漏以及如何预防…

SpringMVC通用后台管理系统源码

整体的SSM后台管理框架功能已经初具雏形&#xff0c;前端界面风格采用了结构简单、 性能优良、页面美观大的Layui页面展示框架 数据库支持了SQLserver,只需修改配置文件即可实现数据库之间的转换。 系统工具中加入了定时任务管理和cron生成器&#xff0c;轻松实现系统调度问…

Glide加载不出图片与请求浏览器资源时中文转码问题

报错代码如图&#xff1a;Image load failed: Failed to load resourse 首先确保你的图片 URL 地址是正确的&#xff0c;可以通过在浏览器中直接访问这个 URL 来测试。另外&#xff0c;确保 URL 地址不包含特殊字符或空格&#xff0c;以免影响加载。 然后确定依赖库没有问题&am…

地平面--高速布线

https://baijiahao.baidu.com/s?id1764139038516816855&wfrspider&forpc 概念 回顾传输线&#xff0c;由任意两条有一定长度的导线组成&#xff0c;一条为信号路径&#xff0c;一条为返回路径。基本电路理论告诉我们&#xff0c;信号是由电流传播的&#xff0c;明确的…

目标检测-One Stage-YOLOv2

文章目录 前言一、YOLOv2的网络结构和流程二、YOLOv2的创新点预处理网络结构训练 总结 前言 根据前文目标检测-One Stage-YOLOv1可以看出YOLOv1的主要缺点是&#xff1a; 和Fast-CNN相比&#xff0c;速度快&#xff0c;但精度下降。&#xff08;边框回归不加限制&#xff09;…

高并发如何保证接口的幂等性?

前言 接口幂等性问题&#xff0c;对于开发人员来说&#xff0c;是一个跟语言无关的公共问题。本文分享了一些解决这类问题非常实用的办法&#xff0c;绝大部分内容我在项目中实践过的&#xff0c;给有需要的小伙伴一个参考。 不知道你有没有遇到过这些场景&#xff1a; 有时我…

Docker(八)Python+旧版本chrome+selenium+oss2+fastapi镜像制作

目录 一、背景二、能力三、核心流程图四、制作镜像1.资源清单2.Dockerfile3.制作镜像 五、启动测试 一、背景 近几年我们线下的创业团队已从零到一开发过好几个小程序项目&#xff0c;都是和体育相关。其中生成海报分享图片好像都是不可或缺的功能。之前的项目老板给的时间都比…

如何让CHAT使用python绘制概率密度图像?

问CHAT&#xff1a;用python绘制概率密度图像 CHAT回复&#xff1a;你可以使用Python的matplotlib库和numpy库进行概率密度的绘制。 以下是一个简单的例子&#xff1a; python import numpy as np import matplotlib.pyplot as plt #随机生成1000个正态分布的数 data np.rand…

《微机原理与应用》期末考试题库(附答案解析)

第1章 微型计算机概述 1.微型计算机的硬件系统包括___A _____。 A&#xff0e;控制器、运算器、存储器和输入输出设备 B&#xff0e;控制器、主机、键盘和显示器 C&#xff0e;主机、电源、CPU和输入输出 D&#xff0e;CPU、键盘、显示器和打印机 2.微处…

安装Keras用于影像分割

conda create -n tfkeras2024 python3.9.18 activate tfkeras2024 pip install tensorflow-gpu2.9.0 pip install keras pip install scipy pip install ipykernel ipython python -m ipykernel install --name tfkeras2024 删除环境conda remove -n tfkeras2024 --all

构建高效秒杀系统的设计原理及注意事项

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…