赞
踩
应用程序调用文件系统的API(open、close、read、write) -> 文件系统根据访问的设备类型,调用对应设备的驱动API -> 驱动对硬件进行操作。
驱动类型:
除网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()
等即可访问字符设备和块设备。
驱动的动态加载和静态加载区别:
obj-y
,动态加载–obj-m
uimag
;动态加载,则是独立的ko,驱动代码不在内核镜像中,而是独立存在于文件系统中uboot
的启动,然后linux内核启动,最后文件系统启动。静态加载,随linux内核一起加载启动,动态加载则需要等文件系统启动后手动insmod加载。动态加载优势:
字符设备必须以串行顺序依次进行访问,常见的字符设备:串口、watchdog、rtc、鼠标、触摸屏等。字符设备的读写以字节为单位。
了解字符设备相关概念和结构体,为之后阅读字符设备驱动代码打下坚实基础。
1 设备号dev_t:类似一个人的身份证号。内核中通过dev_t
来描述设备号,其实质是unsigned int
32位整数,其中高12位为主设备号,低20位为次设备号。
linux如何为每一个设备生成设备号呢?
可以是驱动自行构造一个设备号,然后调用函数register_chrdev_region
向内核注册。如果注册成功,该函数返回0;注册失败,返回一个负数,就不能用这个设备号。
int register_chrdev_region(dev_t from, unsigned count, const char *name);
2 设备描述cdev、file_operations:
一个linux设备,需要通过cdev
结构体来描述设备信息,通过file_operations
来描述如何操作设备。最后要通过cdev_add
函数来注册设备,让linux内核知道这个设备。
/*设备信息的描述*/
struct cdev {
struct kobject kobj; /**/
struct module *owner;
const struct file_operations *ops; /*一组函数集*/
struct list_head list; /*链表*/
dev_t dev; /*设备号*/
unsigned int count; /*已支持的设备*/
}
/*设备行为的描述:驱动设备开发,就要实现这些函数*/
struct file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
...
};
/*相关注册函数*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/fs.h>
#define OK (0)
#define ERROR (-1)
#define DEV_NUM (10)
/*
创建设备ID:MKDEV
注册设备ID:
给设备行为结构体的成员函数赋值
初始化设备:建立设备行为结构体file_operations和设备信息结构体cdev的关系;
添加设备:向内核注册设备,使内核知道设备,建立设备ID和设备信息cdev的关系;
加载设备:insmod
创建设备文件:建立设备文件与设备ID的关系:mknod /dev/my_chr_dev c 232 0
测试设备行为结构体的成员函数功能
*/
dev_t g_dev_num; /*设备号*/
struct cdev *g_cdev; /*设备信息*/
struct file_operations *g_fops; /*设备行为信息*/
const int g_major = 232; /*主设备号*/
const int g_minor = 0; /*次设备号*/
unsigned g_dev_count = 3;
const char g_dev_name[] = "my_char_dev"; /*设备名称,内核管理用的,随便写*/
ssize_t my_dev_read(struct file *my_file, char __user *my_user, size_t my_size, loff_t *my_loff)
{
printk(KERN_EMERG "read my dev success\n");
return OK;
}
ssize_t my_dev_write(struct file *my_file, const char __user *my_user, size_t my_size, loff_t *my_loff)
{
printk(KERN_EMERG "write my dev success\n");
return OK;
}
int my_dev_open(struct inode *my_inode, struct file *my_file)
{
printk(KERN_EMERG "open my dev success\n");
return OK;
}
int my_dev_release(struct inode *my_inode, struct file *my_file)
{
printk(KERN_EMERG "release my dev success\n");
return OK;
}
static int __init chr_init(void)
{
g_dev_num = MKDEV(g_major, g_minor); /*生成设备号*/
printk("dev id:%d\n", g_dev_num);
/*注册设备号*/
if (register_chrdev_region(g_dev_num, g_dev_count, g_dev_name) != 0) {
printk(KERN_EMERG "register dev id failed\n");
return ERROR;
}
/*内核函数:自动生成设备函数, 避免自定义设备号冲突,导致注册失败
if (alloc_chrdev_region(&g_dev_num, g_major, g_dev_count, g_dev_name) != OK) {
printk(KERN_EMERG "register dev id failed\n");
return ERROR;
}
*/
/*为设备信息结构体cdev和设备行为结构体file_operations申请内存;kzalloc相比kmalloc,会将申请的内存地址置为0,*/
g_cdev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
g_fops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
/*为设备行为结构体成员函数赋值*/
g_fops->read = my_dev_read;
g_fops->write = my_dev_write;
g_fops->open = my_dev_open;
g_fops->release = my_dev_release;
g_fops->owner = THIS_MODULE; /*一般就赋值为THIS_MODULE宏*/
cdev_init(g_cdev, g_fops); /*初始化设备:建立cdev和file_operations的联系*/
cdev_add(g_cdev, g_dev_num, g_dev_count); /*向内核注册设备:建立cdev和设备号的联系*/
printk(KERN_EMERG "add my_chr_dev success\n");
return OK;
}
static void __exit chr_exit(void)
{
cdev_del(g_cdev); /*删除设备*/
unregister_chrdev_region(g_dev_num, g_dev_count); /*注销设备号*/
printk(KERN_EMERG "unregister my_chr_dev success\n");
}
module_init(chr_init);
module_exit(chr_exit);
MODULE_LICENSE("GPL");
make编译模块后,insmod my_chr_dev
加载字符设备驱动。
makefile文件解读:
ifneq ($(KERNELRELEASE),)
obj-m := my_chr_dev.o
else
KERDIR ?="/lib/modules/$(shell uname -r)"/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERDIR) M=$(PWD)
clean:
rm -rf *.o *.ko *mod.c 9.module.o *.order
endif
mknod /dev/my_chr_dev c 232 0
,232 0
为主、次设备号。
mknod
,实际上就是建立设备文件my_chr_dev
与设备号232 0
之间的联系。驱动测试程序:
#include <stdio.h>
#include <fcntl.h> /*O_RDWR*/
#include <unistd.h> /*read open write close usleep*/
#define DATA_NUM (10)
int main()
{
int fd;
int wd, rd;
char my_buffer[DATA_NUM] = "12345";
fd = open("/dev/my_chr_dev", O_RDWR);
printf("fd=%d\n", fd);
if (fd == -1){
printf("open failed\n");
return -1;
}
wd = write(fd, my_buffer, DATA_NUM);
rd = read(fd, my_buffer, DATA_NUM);
printf("wd=%d, rd=%d\n", wd, rd);
return 0;
}
该程序用于测试驱动程序的open、write、read、release
。编译执行该驱动程序后,执行dmesg
,可以查看内核调用驱动程序对应的open、write、read、release
函数时日志信息。
开发字符设备驱动主要有7步:
MKDEV
、注册设备号register_chrdev_region
;open/close/read/write
等操作。cdev
与设备行为结构体file_operations
之间的联系:cdev_init
。cdev_add
。make
编译、insmod
加载设备驱动。mknod /dev/chrDev c 主设备号 次设备号
。open/close/read/write
功能。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。