Linux内核驱动编写

Linux内核驱动编写#前言开发过单片机的小伙伴可以看下我之前的一篇文章从单片机开发到linux内核驱动,以浅显易懂的方式带你敲开Linux驱动开发的大门。#正文用户空间的每个函数(用于使用设备或者文件的),在内核空间中都有一个对应的功能相似并且可将内核的信息向用户空间传递的函数。下表为几种设备驱动事件和它们在内核和用户空间对应的接口函数。事件(Events)用户函数(Userfunction…

大家好,又见面了,我是你们的朋友全栈君。

# 前言

开发过单片机的小伙伴可以看一下我之前的一篇文章从单片机开发到linux内核驱动,以浅显易懂的方式带你敲开Linux驱动开发的大门。

# 正文

用户空间的每个函数(用于使用设备或者文件的),在内核空间中都有一个对应的功能相似并且可将内核的信息向用户空间传递的函数。

下表为几种设备驱动事件和它们在内核和用户空间对应的接口函数。

事件(Events) 用户函数(User functions) 内核函数(Kernel functions)
加载模块(Load module) insmod module_init()
打开设备(Open device) fopen file_operations:open
读设备(Read device) fread file_operations:read
写设备(Write device) fwrite file_operations:write
关闭设备(Close device) fclose file_operations:release
卸载模块(Remove module) rmmod module_exit()

下面是一个简单的内存设备驱动,实现了一个字节的写入和读取。

memory.c

/** * <memory initial> * 驱动初始化 */
/* Necessary includes for device drivers */
#include <linux/init.h>
// #include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/uaccess.h> /* copy_from/to_user */
#include <asm/switch_to.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_from/to_user ??? <linux/uaccess.h>*/
#include <linux/proc_fs.h>
MODULE_LICENSE("Dual BSD/GPL");
/* 函数声明 Declaration of memory.c functions */
int memory_open(struct inode *inode, struct file *filp);
int memory_release(struct inode *inode, struct file *filp);
ssize_t memory_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
ssize_t memory_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
void memory_exit(void);
int memory_init(void);
/* file_operations结构体填充 Structure that declares the usual file */
/* access functions */
struct file_operations memory_fops = { 

.read = memory_read,
.write = memory_write,
.open = memory_open,
.release = memory_release,
};
/* Declaration of the init and exit functions */
module_init(memory_init);
module_exit(memory_exit);
/* 全局变量 Global variables of the driver */
/* Major number */
int memory_major = 60; //主设备号 待会 mknod /dev/memory c 60 0 里的60就是这个主设备号
/* Buffer to store data */
char *memory_buffer; //内存指针,将用于存储该内核驱动的数据
/** * <memory init module> * 连接到设备 */
/** * 下面代码使用留kmalloc函数。这个函数工作在内核空间,用于为该驱动程序的缓冲区分配内存。 * 它和我们熟悉的malloc函数很相似。 * 最后,如果注册主设备号或者分配内存失败,模块将退出。 */
int memory_init(void)
{ 

int result;
/* Registering device */
// 在内核空间,把驱动和/dev下设备文件链接在一起。
// 它有三个参数:主设备号,模块名称、file_operations结构体指针
// 在安装模块(insmod)时被调用
result = register_chrdev(memory_major, "memory", &memory_fops);
if (result < 0)
{ 

printk("<1>memory: cannot obtain major number %d\n", memory_major);
return result;
}
/* Allocating memory for the buffer */
memory_buffer = kmalloc(1, GFP_KERNEL);
if (!memory_buffer)
{ 

result = -ENOMEM;
goto fail;
}
memset(memory_buffer, 0, 1);
printk("<1>Inserting memory module\n");
return 0;
fail:
memory_exit();
return result;
}
/** * <memory exit module> * 卸载驱动 */
/* 为了完全地卸载该驱动,缓冲区也要通过该函数进行释放 */
void memory_exit(void)
{ 

/* Freeing the major number */
unregister_chrdev(memory_major, "memory");
/* Freeing buffer memory */
if (memory_buffer)
{ 

kfree(memory_buffer);
}
printk("<1>Removing memory module\n");
}
/** * <memory open> * 像打开文件一样打开设备 */
/** * 内核空间打开文件的函数是open,和用户空间打开文件的函数fopen对应。 * 在本例里,是memory_open函数 * 参数1:inode结构体指针,该结构向内核发送主设备号和从设备号的信息 * 参数2:file结构体指针,用于说明该设备文件允许哪些操作 * * 当设备文件打开后,通常就需要初始化驱动的各个变量,对设备进行复位。但在本例中,这些操作都没进行 */
int memory_open(struct inode *inode, struct file *filp)
{ 

/* Success */
return 0;
}
/** * <memory release> * 像关闭文件一样关闭设备 */
/** * 在内核空间里,和用户空间里关闭文件的fclose对应的函数是release。 * 它是file_operations结构体的成员,用于调用register_chardev。 * 在本例中,它时函数memory_release,和上面相似,他也有两个参数inode和file */
int memory_release(struct inode *inode, struct file *filp)
{ 

/* Success */
return 0;
}
/** * <memory read> * 读取设备 */
/** * 和用户空间函数fread类似,内核空间里,读取设备文件使用read函数 * read是file_operations的成员,在本例中调用memory_read函数 * 参数1:file结构 * 参数2:一个缓冲区 * 参数3:要传输的字节数 * 参数4:f_pos,表示从哪里开始读取该设备文件 * * 本例中,memory_read函数通过copy_to_user函数从驱动的缓冲区memory_buffer向用户空间传送一个简单的字节。 */
ssize_t memory_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{ 

/* Transfering data to user space */
copy_to_user(buf, memory_buffer, 1);
/* Changing reading position as best suits */
if (*f_pos == 0)
{ 

*f_pos += 1;
return 1;
}
else
{ 

return 0;
}
}
/** * <memory write> * 向设备写数据 */
/** * 和用户空间里写文件的fwrite对应,内核空间里时write * write时file_operations的成员,本例中对应memory_write函数 * 函数参数和read类似 * 本例中函数copy_from_user从用户空间传送到内核空间 */
ssize_t memory_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{ 

const char __user *tmp;
tmp = buf + count - 1;
copy_from_user(memory_buffer, tmp, 1);
return 1;
}

Makefile

obj-m = memory.o
all:
make -C /lib/modules/$(shell uname -r)/build/ M=${PWD} modules
clean:
make -C /lib/modules/$(shell uname -r)/build/ M=${PWD} clean

编译(make)、模块加载到内核(insmod)、创建设备文件(mknod)、读写(read/write)、删除设备文件(rm)、从内核移除模块(rmmod)

liyongjun@Box:~/project/c/DRIVERS/memory$ make
make -C /lib/modules/4.15.0-91-generic/build/ M=/home/liyongjun/project/c/DRIVERS/memory modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-91-generic'
CC [M]  /home/liyongjun/project/c/DRIVERS/memory/memory.o
Building modules, stage 2.
MODPOST 1 modules
CC      /home/liyongjun/project/c/DRIVERS/memory/memory.mod.o
LD [M]  /home/liyongjun/project/c/DRIVERS/memory/memory.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-91-generic'
liyongjun@Box:~/project/c/DRIVERS/memory$ sudo insmod memory.ko 
liyongjun@Box:~/project/c/DRIVERS/memory$ sudo mknod /dev/memory c 60 0
liyongjun@Box:~/project/c/DRIVERS/memory$ sudo chmod 777 /dev/memory
liyongjun@Box:~/project/c/DRIVERS/memory$ echo -n "a" > /dev/memory
liyongjun@Box:~/project/c/DRIVERS/memory$ cat /dev/memory
aliyongjun@Box:~/project/c/DRIVERS/memory$ 

insmod的作用是将模块手动地加载到内核中,相当于安装设备的驱动。

但是模块加载到内核中,还是不能用,得有具体的设备才能用。

如果驱动模块中有实现自动生成当前设备文件节点的代码,那么会使用和热拔插相关的代码脚本,自动在/dev下面生成对应的设备文件。

如果没有,只能自己手动来生成这个设备文件。当然最终都要运行mknod命令,它会根据你传进去的主次设备号和类型。在内核维护的设备和驱动列表中寻找你在驱动模块中注册的设备和驱动。如果找到了,会生成相关的文件节点,并在节点内部存下相关驱动的信息,当你打开或者读写文件节点的时候,最终会调用到你注册的驱动中相关的驱动函数。

debug:

编译驱动出现:
warning: initialization from incompatible pointer type
assignment discards ‘const’ qualifier from pointer target type

参考:

mknod和insmod这两条命令产生的文件之间内在有什么关联

Linux device driver中文版.pdf

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159415.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • MT4-MQL4语言EA自动交易编程入门到精通[通俗易懂]

    MT4-MQL4语言EA自动交易编程入门到精通[通俗易懂]MT4-MQL4语言EA自动交易编程入门到精通

  • m-learning_数据库上云

    m-learning_数据库上云这是云从大佬在CVPR上的一篇paper。基本思想就是通过对globalfeature进行多粒度的切分,提取更局部的细节特征。当时在Market-1501,CUHK03,DukeMTMC-reID三个数据集刷新了SOTA纪录,其中最高在Market-1501上的首位命中率(Rank-1Accuracy)达到96.6%,让(ReID)在准确率上首次达到商用水平,很大程度上可以说是推动了整个rei…

  • 常用矩阵范数_矩阵相减的范数

    常用矩阵范数_矩阵相减的范数(1)矩阵的核范数:矩阵的奇异值(将矩阵svd分解)之和,这个范数可以用来低秩表示(因为最小化核范数,相当于最小化矩阵的秩——低秩); (2)矩阵的L0范数:矩阵的非0元素的个数,通常用它来表示稀疏,L0范数越小0元素越多,也就越稀疏。 (3)矩阵的L1范数:矩阵中的每个元素绝对值之和,它是L0范数的最优凸近似,因此它也可以近似表示稀疏; (4)矩阵的F范数:矩阵的各个元素…

  • C/C++程序员必须熟练应用的开源项目

    作为一个经验丰富的C/C++程序员,肯定亲手写过各种功能的代码,比如封装过数据库访问的类,封装过网络通信的类,封装过日志操作的类,封装过文件访问的类,封装过UI界面库等,也在实际的项目中应

    2021年12月27日
  • 什么意思_html5文字居中代码【转载】display:inline-block兼容ie6/7的写法

  • Vue中使用animate.css「建议收藏」

    Vue中使用animate.css「建议收藏」最近把公司官网项目依赖进行了升级,里面用到了animate.css。目前版本4.1.0。目前4.x版本相比之前3.x的breakingchange如下:Animate.cssv4broughtsomeimprovements,improvedanimations,andnewanimations,whichmakesitworthupgrading.Butitalsocomeswithabreakingchange:wehaveaddedpref

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号