文章目录
全系列传送门
Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)
Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写
Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)
Linux嵌入式驱动开发11——平台总线模型修改为设备树实例
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作
Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)
Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)
引言
对于Linux,一切都可以是文件
文件为对应的操作有打开,关闭, 读写
那么
设备节点对应的操作有打开,关闭,读写
如果我在应用层使用系统IO对设备节点进行打开,关闭,读写操作,会发生什么呢?
应用层和内核层关系
开发板的Linux的源码目录下,找到inode_operations结构体定义
include/linux/fs.h
file_operations 文件集
通过搜索查找file_operations 可以找到这个结构体
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};
下面对常见的几个函数来进行介绍
read
对于文件集结构体中,我们来看这个read函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
当我们在应用层read设备节点的时候,就会触发我们驱动里面的read这个函数
write
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
当我们在应用层write设备节点的时候,就会触发我们驱动里面的write这个函数
poll
unsigned int (*poll) (struct file *, struct poll_table_struct *);
当我们在应用层poll/select设备节点的时候,就会触发我们驱动里面的poll/select这个函数
iocontrol
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
当我们在应用层ioctl设备节点的时候,就会触发我们驱动里面的ioctl这个函数
open
int (*open) (struct inode *, struct file *);
当我们在应用层open设备节点的时候,就会触发我们驱动里面的open这个函数
close
int (*release) (struct inode *, struct file *);
当我们在应用层close设备节点的时候,就会触发我们驱动里面的close这个函数
驱动例子
Linux嵌入式应用层和内核层数据传输modules_file_operations
file_operations modules
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
int misc_open (struct inode *inode, struct file *file){
printk("hello misc_open!!!\n");
return 0;
}
int misc_release(struct inode *inode, struct file *file){
printk("bye bye misc_release!!!\n");
return 0;
}
ssize_t misc_read(struct file *file, const char __user *user, size_t size, loff_t *loff_t){
printk("hello misc_read!!!\n");
return 0;
}
ssize_t misc_write(struct file *file, const char __user *user, size_t size, loff_t *loff_t){
printk("hello misc_write!!!\n");
return 0;
}
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc",
.fops = &misc_fops
};
static int misc_init(void)
{
int ret;
ret = misc_register(&misc_dev);
if(ret < 0){
printk("misc_register failed!!!\n");
return -1;
}
printk("misc_register succeed!!!\n"); // 在内核中无法使用c语言库,所以不用printf
return 0;
}
static void misc_exit(void)
{
misc_deregister(&misc_dev);
printk("misc exit!!!\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
这里的打印使用的是printk
,因为这个是在内存中打印,所以不是常见的printf
编译 加载模块
编译后,安装模块到开发板
查看模块列表
lsmod
查看设备节点有没有问题
ls /dev
可以看到我们自己写的驱动模块
app
有了驱动代码,下面我们还需要的就是应用层的代码,来实现我们要实现的一些功能。
代码
这个代码主要就是调用了read和write,然后看看能不能正常的使用
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char buff[64] = {0};
fd = open("/dev/hello_misc", O_RDWR);
if(fd < 0){
perror("open error\n"); // 在应用中打印
return fd;
}
read(fd, buff, sizeof(buff));
write(fd, buff,sizeof(buff));
close(fd);
return 0;
}
perror
是在应用中的打印函数
编译
对于飞凌的开发板,不能直接使用
arm-poky-linux-gnueabi-gcc app.c -o app
因为要有环境变量的配置,但是飞凌给我们写好了
查看 /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setuo-cortexa9hf-neon-poky-linux-gnueabi 文件可以发现在大约11行的地方export CC=”arm-poky-linux-gnueabi-gcc -march=armv7-a…“
所以,gcc的参数已经声明成环境变量CC了,所以对于飞凌的这个开发板,只需要执行$CC
,就相当于执行了一系列的环境配置arm-poky-linux-gnueabi-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a9 --sysroot=$SDKTARGETSYSROOT
实际操作如下
$CC app.c -o app
全部编译好之后下载到开发板
scp app root@192.168.0.232:/tmp
验证
来到我们的开发板上
确保我们的file_operations驱动加载成功后,执行app可执行文件
小结
我们上面的过程,可以总结成这个框图
中间的设备接地那就是应用层和内核层的桥梁
-
如果我们的file_operations里面没有read,我们在应用层read设备节点的时候会发生什么?
-
答案是什么也不会发生,也不会报错
上面的这套过程,就编写了一个的驱动模块,主要用到的就是file_operations文件集的函数
应用层和内核层数据层传输
应用层和内核层不能直接数据层传输
这两个函数,只可以在驱动中使用,来完成我们的应用层和应用层的桥梁功能
static inline long copy_from_user(void *to, const void _user *from, unsigned long n)
static inline long copy_to_user(void _user *to, const void *from, unsigned long n)
read功能实践
file_operations.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
int misc_open (struct inode *inode, struct file *file){
printk("hello misc_open!!!\n");
return 0;
}
int misc_release(struct inode *inode, struct file *file){
printk("bye bye misc_release!!!\n");
return 0;
}
ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
char kbuf[64] = "copy to user!!!\n";
if( copy_to_user(ubuf, kbuf, size) != 0 ){
printk("copy_to_user error!!!\n");
return -1;
}
printk("hello misc_read!!!\n");
return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
printk("hello misc_write!!!\n");
return 0;
}
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc",
.fops = &misc_fops
};
static int misc_init(void)
{
int ret;
ret = misc_register(&misc_dev);
if(ret < 0){
printk("misc_register failed!!!\n");
return -1;
}
printk("misc_register succeed!!!\n"); // 在内核中无法使用c语言库,所以不用printf
return 0;
}
static void misc_exit(void)
{
misc_deregister(&misc_dev);
printk("misc exit!!!\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
这段代码的核心也就是下面这一段,我们可以看到copy_to_user(ubuf, kbuf, size)
。
这样在应用层调用read来读取驱动模块的时候,就可以启动misc_read
函数来执行操作,把内核层的数据kbuf
发送到应用层的接口
ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
char kbuf[64] = "copy to user!!!\n";
if( copy_to_user(ubuf, kbuf, size) != 0 ){
printk("copy_to_user error!!!\n");
return -1;
}
printk("hello misc_read!!!\n");
return 0;
}
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char buff[64] = "app test copy from user";
fd = open("/dev/hello_misc", O_RDWR);
if(fd < 0){
perror("open error\n"); // 在应用中打印
return fd;
}
read(fd, buff, sizeof(buff));
printf("buf is:%s\n", buff);
close(fd);
return 0;
}
read(fd, buff, sizeof(buff));
这个就是我们上面说的来启动和接收内核层发送过来的数据的函数,这样就可以把我们内核层中的kbuf
数据,发送到了应用层的buff
中。
开发板的运行结果如图所示
write功能实践
file_operations.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
int misc_open (struct inode *inode, struct file *file){
printk("hello misc_open!!!\n");
return 0;
}
int misc_release(struct inode *inode, struct file *file){
printk("bye bye misc_release!!!\n");
return 0;
}
ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
char kbuf[64] = "copy to user!!!\n";
if( copy_to_user(ubuf, kbuf, size) != 0 ){
printk("copy_to_user error!!!\n");
return -1;
}
printk("hello misc_read!!!\n");
return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
char kbuf[64] = "copy from user!!!\n";
if( copy_from_user(kbuf, ubuf, size) != 0 ){
printk("copy_from_user error!!!\n");
return -1;
}
printk("buf is:%s\n", kbuf);
printk("hello misc_write!!!\n");
return 0;
}
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc",
.fops = &misc_fops
};
static int misc_init(void)
{
int ret;
ret = misc_register(&misc_dev);
if(ret < 0){
printk("misc_register failed!!!\n");
return -1;
}
printk("misc_register succeed!!!\n"); // 在内核中无法使用c语言库,所以不用printf
return 0;
}
static void misc_exit(void)
{
misc_deregister(&misc_dev);
printk("misc exit!!!\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
对于驱动中的misc_write来说,跟上面的misc_read差不多,只是需要注意,这一个写操作,是从应用层的buff
来写到内核层的kbuf
中,并且使用copy_from_user(kbuf, ubuf, size)
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
char kbuf[64] = "copy from user!!!\n";
if( copy_from_user(kbuf, ubuf, size) != 0 ){
printk("copy_from_user error!!!\n");
return -1;
}
printk("buf is:%s\n", kbuf);
printk("hello misc_write!!!\n");
return 0;
}
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char buff[64] = "app test copy from user";
fd = open("/dev/hello_misc", O_RDWR);
if(fd < 0){
perror("open error\n"); // 在应用中打印
return fd;
}
// read(fd, buff, sizeof(buff));
write(fd, buff,sizeof(buff));
// printf("buf is:%s\n", buff);
close(fd);
return 0;
}
write(fd, buff,sizeof(buff));
这个就是我们上面说把应用层的数据发送到内核层数据的函数,这样就可以把我们应用层中的buff
数据,发送到了内核中的kbuf
中。