驱动开发相关文章:
驱动开发(一):驱动代码的基本框架
驱动开发(二):创建字符设备驱动
驱动开发(三):内核层控制硬件层
驱动开发(四):Linux内核中断
驱动开发(五):Linux内核定时器
驱动开发(六):应用层通过文件系统与内核层交互
驱动开发(应用):设计一个社区饮水机控制系统
应用层与内核层的交互方式
在Linux驱动开发中,应用层可以通过以下几种方式来控制内核层:
- 文件系统接口:应用程序可以通过文件系统接口来与内核驱动进行通信。应用程序可以通过打开、读取、写入和关闭文件等操作来发送控制命令或获取设备状态。
- 系统调用:应用程序可以通过系统调用来请求内核执行某些操作。例如,应用程序可以使用ioctl()系统调用来发送特定的命令给内核驱动。
- proc文件系统:proc文件系统提供了一种特殊的文件系统接口,允许应用程序通过读写虚拟文件来与内核进行通信。应用程序可以通过读写/proc文件系统中的特定文件来发送控制命令或获取设备状态。
- sysfs文件系统:sysfs文件系统是Linux内核提供的一种特殊文件系统,用于向用户空间提供硬件设备的信息。应用程序可以通过读写sysfs文件来控制设备的行为。
- netlink套接字:Netlink是一种用于内核与用户空间之间进行通信的机制。应用程序可以通过创建和使用Netlink套接字来与内核驱动进行通信。
这些方式都可以让应用层与内核层进行交互和控制,具体选择哪种方式取决于应用的需求和设备的特性。
通过文件系统交互
在Linux驱动开发中,通过文件系统进行应用层和内核层的交互涉及以下几个步骤:
- 定义设备节点:在内核驱动中,需要定义设备节点,即创建一个特殊的文件。这可以通过调用
register_chrdev()
函数来完成。该函数会分配一个主设备号和次设备号,并创建对应的设备节点。 - 实现设备文件操作函数:在内核驱动中,需要实现设备文件操作函数,包括
open()
、read()
、write()
和release()
等。这些函数会在应用程序对设备文件执行相应操作时被内核调用。 - 注册设备文件操作函数:通过调用
file_operations
结构体中的相应函数指针来注册设备文件操作函数,这样内核就会在对设备文件执行相应操作时调用相应的函数。 - 用户空间操作设备文件:在应用层,应用程序可以通过文件系统接口(如open()、read()、write()和close()等函数)来操作设备文件。例如,应用程序可以使用open()函数打开设备文件,使用write()函数向设备发送命令,使用read()函数读取设备的状态,并使用close()函数关闭设备文件。
- 内核层处理设备文件操作:当应用程序调用文件系统接口函数时,内核会相应地调用对应的设备文件操作函数。在这些设备文件操作函数中,可以对设备进行相应处理,如处理读写请求、控制设备的操作等。
通过以上步骤,应用层和内核层就可以通过文件系统进行交互了。应用程序通过操作设备文件,触发设备文件操作函数的调用,从而实现与内核驱动的通信和控制。
file_operations 结构体
file_operations
结构体是Linux内核中用于注册设备文件操作函数的重要结构体。它定义在 <
linux/fs.h
头文件中。
file_operations
结构体可以在内核驱动中使用,将设备文件操作函数与对应的设备文件关联起来。
file_operations
结构体的常用函数及注释如下:
struct file_operations {
struct module *owner; // 拥有此结构体的模块的指针,用于管理模块的引用计数
loff_t (*llseek) (struct file *, loff_t, int);
// 设置文件指针位置的函数,可用于在读写文件时跳过一定的字节数
// 参数:
// - struct file *:文件描述符指针
// - loff_t:偏移量,表示需要移动的字节数
// - int:位置标志,用于确定移动的起始位置(SEEK_SET,SEEK_CUR,SEEK_END)
// 返回值:
// - loff_t:调整后的文件指针位置
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
// 读取设备文件内容的函数,在read系统调用中被调用
// 参数:
// - struct file *:文件描述符指针
// - char __user *:用户空间缓冲区指针,用于存储读取的数据
// - size_t:读取的数据大小
// - loff_t *:文件指针位置的指针,用于更新读取后的文件指针位置
// 返回值:
// - ssize_t:实际读取的数据大小,若失败则返回错误码
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
// 写入数据到设备文件的函数,在write系统调用中被调用
// 参数:
// - struct file *:文件描述符指针
// - const char __user *:用户空间缓冲区指针,包含要写入的数据
// - size_t:要写入的数据大小
// - loff_t *:文件指针位置的指针,用于更新写入后的文件指针位置
// 返回值:
// - ssize_t:实际写入的数据大小,若失败则返回错误码
int (*open) (struct inode *, struct file *);
// 打开设备文件时被调用的函数,在open系统调用中被调用
// 参数:
// - struct inode *:文件的inode节点指针
// - struct file *:文件描述符指针
// 返回值:
// - int:成功返回0,否则返回错误码
int (*release) (struct inode *, struct file *);
// 关闭设备文件时被调用的函数,在close系统调用中被调用
// 参数:
// - struct inode *:文件的inode节点指针
// - struct file *:文件描述符指针
// 返回值:
// - int:成功返回0,否则返回错误码
// 其他成员函数...
};
除了上述函数外,
file_operations
结构体还包括其他一些函数指针,如
poll
、
ioctl
、
mmap
等,用于实现更高级的设备文件操作。
在内核驱动中,可以通过定义一个自定义的
file_operations
结构体,并将其与设备文件关联起来,以注册设备文件操作函数。这样,在应用程序通过文件系统接口操作设备文件时,相应的设备文件操作函数将会被内核调用,实现设备的处理和控制。例如,可以通过调用
cdev_init()
函数将自定义的
file_operations
结构体注册到字符设备驱动中。
应用程序将数据传递给驱动
从用户空间拷贝数据到内核空间
#include <linux/uaccess.h>
int copy_from_user(void *to, const void __user *from, int n)
功能:从用户空间拷贝数据到内核空间
参数:
@to :内核中内存的首地址
@from:用户空间的首地址
@n :拷贝数据的长度(字节)
返回值:成功返回0,失败返回未拷贝的字节的个数
从内核空间拷贝数据到用户空间
int copy_to_user(void __user *to, const void *from, int n)
功能:从内核空间拷贝数据到用户空间
参数:
@to :用户空间内存的首地址
@from:内核空间的首地址 __user需要加作用是告诉编译器这是用户空间地址
@n :拷贝数据的长度(字节)
返回值:成功返回0,失败返回未拷贝的字节的个数
示例代码
驱动代码(hello.c)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define CNAME "hello"
int copy_size = 0;
char kbuf[128] = {0};
unsigned int major = 0;
ssize_t mycdv_read(struct file *file, char __user *ubuf, size_t len, loff_t *loff)
{
if (len > sizeof(kbuf)) //len是用户需要的,有可能大于内核传的
{
len = sizeof(kbuf); //大于的时候,有多少就给它多少
}
copy_size = copy_to_user(ubuf, kbuf, len);
if (copy_size)
{
printk(KERN_ERR "copy_to_user error\n");
return copy_size;
}
return 0;
}
ssize_t mycdv_write(struct file *file, const char __user *ubuf, size_t len, loff_t *lo)
{
if (len > sizeof(kbuf))
{
len = sizeof(kbuf);
}
copy_size = copy_from_user(kbuf, ubuf, len);
if (copy_size)
{
printk(KERN_ERR "copy_from_user error\n");
return copy_size;
}
return 0;
}
int mycdv_open(struct inode *inode, struct file *file)
{
printk(KERN_ERR "mycdv open ok\n");
return 0;
}
int mycdv_release(struct inode *inode, struct file *file)
{
printk(KERN_ERR "mycdv release ok\n");
return 0;
}
const struct file_operations fops =
{
.read = mycdv_read,
.write = mycdv_write,
.open = mycdv_open,
.release = mycdv_release,
};
static int __init hello_init(void)
{
major = register_chrdev(major, CNAME, &fops);
if (major < 0)
{
printk(KERN_ERR "register chrdev ERR\n");
return major;
}
return 0;
}
static void __exit hello_exit(void)
{
unregister_chrdev(major, CNAME);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
应用层(main.c)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char buf[128]="this is my_cdev";
int main(int argc, char const *argv[])
{
int fd;
fd = open("./hello",O_RDWR);
if(fd<0)
{
perror("open err");
return -1;
}
write(fd,buf,sizeof(buf));
memset(buf,0,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("这是读出来的:%s\n",buf);
close(fd);
return 0;
}
版权归原作者 爱做梦的小涛 所有, 如有侵权,请联系我们删除。