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个字节,作为结束null
				protocol_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 +=-O1

PWD = $(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.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC [M]  /home/admin01/file-shield/file_hook.mod.o
  LD [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.mfbz.cn/a/276576.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中北大学 软件构造 U+及上课代码详解

作业1 1.数据类型可分为两类:(原子类型) 、结构类型。 2.(数据结构)是计算机存储、组织数据的方式&#xff0c;是指相互之间存在一种或多种特定关系的数据元素的集合 3.代码重构指的是改变程序的(结构)而不改变其行为&#xff0c;以便提高代码的可读性、易修改性等。 4.软件实…

Kubeadmin实现k8s集群:

Kubeadmin来快速搭建一个k8s集群&#xff1a; 二进制搭建适合大集群&#xff0c;50台以上的主机&#xff0c; 但是kubeadm更适合中小企业的业务集群 环境&#xff1a; Master&#xff1a;20.0.0.71 2核4G 或者4核8G docker kubelet kubectl flannel Node1&#xff1a;20.…

【51单片机系列】DS18B20温度传感器扩展实验之设计一个智能温控系统

本文是关于DS18B20温度传感器的一个扩展实验。 文章目录 一、相关元件介绍二、实验分析三、proteus原理图设计四、软件设计 本扩展实验实现的功能&#xff1a;利用DS18B20设计一个智能温度控制系统&#xff0c;具有温度上下限值设定。当温度高于上限值时&#xff0c;电机开启&a…

首发卡密引流系统 支持短视频点赞/关注获取卡密

搭建教程&#xff1a; 环境要求&#xff1a;Nginx、MySQL 5.6、PHP 5.6 步骤&#xff1a; 将压缩包解压至网站根目录。 打开域名/install&#xff0c;按照提示填写数据库信息进行安装。 管理后台&#xff1a; URL&#xff1a;域名/admin 账号密码&#xff1a;admin/123456 …

基于element ui封装table组件

效果图&#xff1a; 1.封装表格代码如下 <template> <div><div class"TableList"><el-tablev-loading"loading"selection-change"selectionChange"class"table":data"tableData":border"hasBorde…

综合服务IntServ,资源预留协议RSVP以及区分服务DiffServ

目录 1.IntServ 2.IntServ/RSVP 3.区分服务&#xff08;DiffServ&#xff09; 1.区分服务的基本概念 2.PHB&#xff08;每跳行为&#xff09; 1.IntServ IntServ可对单个的应用会话提供服务质量的保证&#xff0c;其主要特点: (1)资源预留。一个路由器需要知道给不断出现…

使用Microsoft托管密钥的Azure信息保护云退出

由于各种原因&#xff0c;一些组织需要一个明确定义的流程来停止使用 Azure 信息保护以及对云服务的任何依赖&#xff0c;而不会在采用之前失去对其数据的访问权限 - 以便在出现需要时做好准备。 Azure 信息保护 (AIP) 为使用自带密钥 (BYOK) 的客户和使用 Microsoft 托管密钥…

[玩转AIGC]LLaMA2之如何跑llama2.c的chat模式

前言&#xff1a;之前我们关于llama2的相关内容主要停留在gc层面&#xff0c;没介绍chat模式&#xff0c;本文将简单介绍下llama2.c的chat模式如何跑起来。训练就算了&#xff0c;没卡训练不起来的&#xff0c;但是用CPU来对别人训练好的模型进行推理还是绰绰有余的&#xff0c…

鸿蒙Harmony(十一)Stage模型

Stage模型&#xff1a;HarmonyOS 3.1 Developer Preview版本开始新增的模型&#xff0c;是目前主推且会长期演进的模型。在该模型中&#xff0c;由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的“舞台”&#xff0c;因此称这种应用模型为Stage模型。 UIAb…

键盘字符(#键)显示错误

当屏幕上显示的键与键盘上按下的键不同时&#xff0c;尤其是 # 键。大多数情况下&#xff0c;此错误是由于 raspbian 和 NOOBS 软件的默认英国键盘配置所致。 解决方案&#xff1a; 要解决此问题&#xff0c;您需要将配置更改为您自己的键盘或语言的配置。这可以通过转到树莓派…

65.乐理基础-打拍子-前附点、后附点

内容来源于&#xff1a;三分钟音乐社 上一个内容&#xff1a;前八后十六、前十六后八拍子-CSDN博客 前附点指的是一个附点八分音符加一个十六分音符的节奏型&#xff0c;如图1。 后附点指的是一个十六分音符加一个附点八分音符的节奏型&#xff0c;如图2。 前附点、后附点这两…

Java开发框架和中间件面试题(10)

目录 104.怎么保证缓存和数据库数据的一致性&#xff1f; 105.什么是缓存穿透&#xff0c;什么是缓存雪崩&#xff1f;怎么解决&#xff1f; 106.如何对数据库进行优化&#xff1f; 107.使用索引时有哪些原则&#xff1f; 108.存储过程如何进行优化&#xff1f; 109.说说…

大厂前端面试题总结(百度、字节跳动、腾讯、小米.....),附上热乎面试经验!

先简单介绍下自己&#xff0c;我“平平无奇小天才”一枚&#xff0c;毕业于南方普通985普通学生&#xff0c;有幸去百度、字节面试&#xff0c;感觉大公司就是不一样&#xff0c;印象最深的是字节&#xff0c;所以有必要总结一下面试经验&#xff0c;以及面试中遇到的一些问题&…

QT应用篇 三、QML自定义显示SpinBox的加减按键图片及显示值效果

QT应用篇 一、QT上位机串口编程 二、QML用Image组件实现Progress Bar 的效果 三、QML自定义显示SpinBox的加减按键图片及显示值效果 文章目录 QT应用篇前言一、qml需求二、使用组件1.SpinBox组件2.SpinBox中QML的使用 总结 前言 记录自己学习QML的一些小技巧方便日后查找 QT的…

unity学习笔记----游戏练习02

一、阳光值的展示和消耗 1.创建一个文本组件用于显示阳光的数值&#xff0c;然后在脚本中得到这个UI。 在SunManger中得到这个组件的引用 public TextMeshProUGUI sunPointText; 写一个用于更新显示的方法 public void UpdataSunPointText() { sunPointText.tex…

多模态大模型-CogVLm 论文阅读笔记

多模态大模型-CogVLm 论文阅读笔记 COGVLM: VISUAL EXPERT FOR LARGE LANGUAGEMODELS 论文地址 :https://arxiv.org/pdf/2311.03079.pdfcode地址 : https://github.com/THUDM/CogVLM时间 : 2023-11机构 : zhipuai,tsinghua关键词: visual language model效果:&#xff08;2023…

小型企业成为网络犯罪分子获取数据的目标

在过去十年的大部分时间里&#xff0c;网络犯罪的巨额资金来自针对大型组织的勒索软件攻击。这种威胁仍然存在。但犯罪分子可能会将注意力转向中小企业 (SMB)。这对消费者的影响将是巨大的。 将软件即服务 (SaaS) 技术用于核心业务功能继续将中小企业整合到全球供应链中。由于…

鸿蒙APP的代码规范

鸿蒙APP的代码规范是为了确保代码质量、可读性和可维护性而定义的一系列规则和标准。以下是一些建议的鸿蒙APP代码规范&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 代码风格&#xff1a; 采用…

【 YOLOv5】目标检测 YOLOv5 开源代码项目调试与讲解实战(4)-自制数据集及训练(使用makesense标注数据集)

如何制作和训练自己的数据集 看yolov5官网创建数据集1.搜索需要的图片2.创建标签标注数据集地址&#xff1a;放入图片后选择目标检测创建文档&#xff0c;每个标签写在单独的一行上传结果此处可以编辑类别把车框选选择类别即可导出数据 3.新建一个目录放数据写yaml文件 4. 测试…

安装Node修改Node镜像地址搭建Vue脚手架创建Vue项目

1、安装VSCode和Node 下载VSCode Visual Studio Code - Code Editing. Redefined 下载Node Node.js (nodejs.org) 检验是否安装成功&#xff0c;WinR,输入cmd命令&#xff0c;使用node -v可以查看到其版本号 2、修改镜像地址 安装好node之后&#xff0c;开始修改镜像地址 …
最新文章