linux用户态与内核态通过字符设备交互
简述
Linux设备分为三类,字符设备、块设备、网络接口设备。字符设备只能一个字节一个字节读取,常见外设基本都是字符设备。块设备一般用于存储设备,一块一块的读取。网络设备,Linux将对网络通信抽象成一个设备,通过套接字对其进行操作。
对于字符设备的用户态与内核态交互,主要涉及到打开、读取、写入、关闭等操作。通过字符设备实现内核与用户程序的交互,设计实现一个内核态监控文件目录及文件复制拷贝的内核模块程序,其中字符设备交互时序图如下:
#mermaid-svg-PVFQMi5OcXErZgMD {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-PVFQMi5OcXErZgMD .error-icon{fill:#552222;}#mermaid-svg-PVFQMi5OcXErZgMD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PVFQMi5OcXErZgMD .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-PVFQMi5OcXErZgMD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PVFQMi5OcXErZgMD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PVFQMi5OcXErZgMD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PVFQMi5OcXErZgMD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PVFQMi5OcXErZgMD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PVFQMi5OcXErZgMD .marker.cross{stroke:#333333;}#mermaid-svg-PVFQMi5OcXErZgMD svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PVFQMi5OcXErZgMD .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PVFQMi5OcXErZgMD text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-PVFQMi5OcXErZgMD .actor-line{stroke:grey;}#mermaid-svg-PVFQMi5OcXErZgMD .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-PVFQMi5OcXErZgMD .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-PVFQMi5OcXErZgMD #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-PVFQMi5OcXErZgMD .sequenceNumber{fill:white;}#mermaid-svg-PVFQMi5OcXErZgMD #sequencenumber{fill:#333;}#mermaid-svg-PVFQMi5OcXErZgMD #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-PVFQMi5OcXErZgMD .messageText{fill:#333;stroke:#333;}#mermaid-svg-PVFQMi5OcXErZgMD .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PVFQMi5OcXErZgMD .labelText,#mermaid-svg-PVFQMi5OcXErZgMD .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-PVFQMi5OcXErZgMD .loopText,#mermaid-svg-PVFQMi5OcXErZgMD .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-PVFQMi5OcXErZgMD .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PVFQMi5OcXErZgMD .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-PVFQMi5OcXErZgMD .noteText,#mermaid-svg-PVFQMi5OcXErZgMD .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-PVFQMi5OcXErZgMD .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PVFQMi5OcXErZgMD .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PVFQMi5OcXErZgMD .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PVFQMi5OcXErZgMD .actorPopupMenu{position:absolute;}#mermaid-svg-PVFQMi5OcXErZgMD .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-PVFQMi5OcXErZgMD .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PVFQMi5OcXErZgMD .actor-man circle,#mermaid-svg-PVFQMi5OcXErZgMD line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-PVFQMi5OcXErZgMD :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;}
user_space
kernel_space
发送监控目录信息(list)
回复监控目录信息已设置
user_space
kernel_space
通信协议格式
[2bytes数据长度] + |2bytes目录路径数量| + |2bytes 长度| + |目录数据| + ... + |2bytes 长度| + |目录数据|
控制命令定义
#include
#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
#include
#includ服务器托管网e
#include
#include
#include
#include
#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("节点指针赋值,%pn",*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(argc2 || 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; iargc; 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,总空间大小 %dn",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("组包数据:%dn",index);
for(i=0; iindex+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("从内核态读取数据:%sn",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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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_indexnum_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 "接收数据:%sn",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 cmdn");
break;
case IOCTL_WRITE_INT:
ret = __get_user(ioarg, (int*)arg);
printk(KERN_INFO "get data from user space is :%dn", ioarg);
break;
case IOCTL_READ_INT:
ioarg = 1101;
ret = __put_user(ioarg, (int *)arg);
break;
case IOCTL_WRITE_STR服务器托管网ING:
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 "从用户态拷贝数据失败,失败字节数 %dn",ret);
}
printk(KERN_INFO "get data from user space is :%*phn",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 : %sn",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 = %dn",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 completelyn");
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
#include
#include
#include
#include
#include
#include
#include
#include
#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.fwqtg.net
相关推荐: 掌握Python库的Bokeh,就能让你的交互炫目可视化
本文分享自华为云社区《Bokeh图形魔法:掌握绘图基础与高级技巧,定制炫目可视化》,作者: 柠檬味拥抱。 Bokeh是一个用于创建交互式可视化图形的强大Python库。它不仅易于使用,而且功能强大,适用于各种数据可视化需求。本文将介绍Bokeh库的服务器托管网…