概要:上一篇我们编写了一个简单的驱动程序,似乎有点索然无味,其实我也是这样觉得的,所以这篇我们将加大力度。
看了上一篇的字符设备似乎觉得很简单,事实不然。
如何注册字符设备
先来看几个函数:
int register_chrdev_region(dev_t dev_id, unsigned count, const char *name)
静态注册字符设备,自己指定主设备号。什么叫静态,就是你自己静静地动手注册的这个状态,就叫静态,自己动手注册,累人。
dev_id:指定了设备号的起始地址,我们用MKDEV(major,minor)来指定,起始主设备号是major,起始次设备号为minor
count: 你想一次性注册次设备号的个数
*name:名字随意 ,比如”sun_xiao_chuan“
注意:判断返回值,小于0就说明注册失败了。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
动态注册字符设备,内核帮你指定主设备。什么叫动态,就是内核动起来帮你注册的这个状态,就叫动态,我还是很喜欢别人自己动的,咳咳,有点跑题
*dev:内核帮你分配后总得告诉你,它的主次设备号吧,它分配完之后就放进dev里面
baseminor:次设备号的起始号
count:你想一次性注册次设备号的个数
name:名字随意,比如"dai_dai_da_shi_xiong"
注意:判断返回值,小于0就说明注册失败了。
对于这个两个函数看实际情况使用,又或者说你喜欢自己动还是别人动,比如我喜欢别人动,那就这么注册字符设备:
alloc_chrdev_region(&devid, 0, 3, "hello");
//那么我就让内核帮我分配字符设备,分配完之后总得告诉我主设备吧?
major = MAJOR(devid)
//使用这个宏定义来获取主设备
有始有终,你在入口函数注册了我,我就在出口函数注销你
unregister_chrdev_region(MKDEV(major, 0), 3);
在出口函数,使用这个函数可以把它注销掉
字符设备的精髓:file_operations:
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
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 (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, 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 (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
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 (*dir_notify)(struct file *filp, unsigned long arg);
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);
};
根据自己需求来写file_operafile,比如:
static struct const file_operation hello_fops = {
.owner = THIS_MODULE, //这个fops只属于我这个字符设备
.open = hello_open, //每当有人打开了我这个字符设备就会执行 hello_open,或者说你来打开我的字符设备我就会找小电影
};
那么完成了自己的fops,怎么告诉内核这个fops是我字符设备的fops:
我们需要使用一个结构体cdev:
struct cdev {
struct kobject kobj; // 内嵌的kobject对象
struct module *owner; //所属模块
const struct file_operations *ops; //操作方法结构体
struct list_head list; //与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; //起始设备编号,可以通过MAJOR(),MINOR()来提取主次设备号
unsigned int count; //连续注册的次设备号个数
}
使用cdev之前进行初始化:
static struct cdev hello_dev;
void cdev_init(&hello_dev, &hello_fops);
cdev:这里装了这个字符设备基本信息。内核就像xx局一样,你要去xx局弄一张身份证,你交一大堆材料,xx局确认你是个人之后,给你一张身份证,从此中国又多了一个人。这个身份证就是cdev。
file_operations:这里就是我们刚刚创建的那个能找小电影的fops
回想一下,我们之前注册了字符设备,但好像内核并没有把我的字符设备放进内核吧
cdev_add(&hello_cdev, devid,2);
这一步就是把我的cdev告诉了内核,或者说就是把我这个字符设备的主次设备、fops,告诉了内核。
void cdev_del(&hello_cdev);
在出口函数,使用这个函数删除这个结构体
到这一步起始我们的驱动程序就能用起来了:
我这篇文章的例子用到的是动态注册字符设备,主设备号我们自己要动手拿出来。然后我还是要谈一下fops的归属问题,我自己写的这个fops有谁能用,假设我们动态注册的major(主设备号)为255,minor(次设备)是从1开始的,次设备号的个数为3,那么major为255,minor从1到3的这些字符设备都拥有我写的fops
我们还是老样子
1.编写makefile
2.使用make编译
3.把ko文件拷贝到开发板上
4.insmod hello.ko
我们的驱动就被加载进内核了,我们来编写一个测试程序
****hello_test.c****
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char **argv)
{
int fd;
fd=open(argv[1],O_RDWR); //我们的fops只有.open函数,所以我们就只打开/dev/hello
if(fd<0)
printf("can't open %s \n",argv[1]);
else
printf("can open %s \n",argv[1]);
return 0;
}
5.编译测试程序:arm-linux-gcc
6.拷贝到开发板
7.运行测试程序
./hello_test /dev/hello
这时候百分百是打印:can’t open /dev/hello,我们打开失败了,为什么?
答:因为在/dev目录下根本没有hello这个字符设备节点,所以我们要创建这个文件。
8 创建设备节点之前我们先查看一下内核分配的主设备号是多少
使用命令:cat /proc/device 得到major为255
9.使用mknod命令设备节点
mknod /dev/hello c 255 1
mknod /dev/hello1 c 255 2
mknod /dev/hello2 c 255 3
//第一个参数是设备名字
//设备的类型
//主设备号
//次设备号
- 运行测试程序:
./hello_test /dev/hello1
./hello_test /dev/hello2
./hello_test /dev/hello3
输出:
can open /dev/hello1
can open /dev/hello2
can open /dev/hello3
自动创建设备节点
我们这样自己这样一个一个地创建设备节点真的很累,我说过我喜欢别人来动,所以内核有提供自动创建设备节点的函数。
static struct class *cls;
cls = class_create(THIS_MODULE, "hello");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0");
class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1");
class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2");
创建名为“hello”的这个类
这个类下面有 “hello0” ,“hello1” ,“hello2”,这些设备节点
class_device_destroy(cls, MKDEV(major, 0));
class_device_destroy(cls, MKDEV(major, 1));
class_device_destroy(cls, MKDEV(major, 2));
class_destroy(cls);
在出口函数,使用这些函数来卸载设备节点和类
重新make ,拷贝到开发板,加载驱动程序:
insmod hello.ko
查看/dev目录下有没有名为“hello*”的设备节点
ls -l /dev/hello*
运行测试程序:
./hello_test /dev/hello1
./hello_test /dev/hello2
./hello_test /dev/hello3
一步成功,完全不需要自己手动创建
到这里字符设备被我加载进内核,并且测试程序能调用到fops里面的hello_open()函数
*****hello.c*****
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/list.h>
#include <linux/cdev.h>
static int major;
static int hello_open(struct inode *inode, struct file *file)
{
printk("hello_open\n");
return 0;
}
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open, //自己编写的open()函数
};
static struct cdev hello_cdev;
static struct class *cls;
static int hello_init(void)//入口函数
{
dev_t devid;
major = register_chrdev(0, "hello", &hello_fops);
if (major) //静态注册
{
devid = MKDEV(major, 0);
register_chrdev_region(devid, 3, "hello");
}
else //动态注册
{
alloc_chrdev_region(&devid, 0, 3, "hello");
major = MAJOR(devid);
}
cdev_init(&hello_cdev, &hello_fops); //初始化cdev结构体
cdev_add(&hello_cdev, devid, HELLO_CNT); //向内核添加cdev结构体
cls = class_create(THIS_MODULE, "hello"); //自动创建设备节点
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */
return 0;
}
static void hello_exit(void) //出口函数
{
class_device_destroy(cls, MKDEV(major, 0));
class_device_destroy(cls, MKDEV(major, 1));
class_device_destroy(cls, MKDEV(major, 2));
class_destroy(cls);
cdev_del(&hello_cdev);
unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");