参考书籍:《Linux设备驱动程序》
(一)编写驱动程序有三个要素,主次设备号,设备文件(又叫设备结点),设备操作方法。怎样在驱动中将三者联系起来成为理解驱动的重要途径,我们通过创建设备号的动态静态方式,创建设备文件的自动手动方式来理解驱动。
(二)主次设备号
1.设备号是通过两个个整数来描述驱动对应设备,其中主设备号对应驱动程序,次设备号对应设备序号。也就是多个设备可能公用一个驱动,也就是公用一个主设备号。次设备号相同不表明他们之间有关系,它主要是用来区分驱动相同的设备。
2.内核中通过dev_t类型来保存,在不同的内核版本上它的实现可能不同,我们也不需要深究,只需要它保存了主设备号和此设备号即可,要通过这个变量获取设备号方法为: MAJOR(dev_t);//获取主设备号
MINOR(dev_t);//获取次设备号
MKDEV(int major,int minor);//获取次设备号变量
3.创建设备号有两种办法,动态和静态,静态分配有设备号已被占用的风险。
①静态:
register_chrdev_region(dev_t first,unsigned int count,char *name);
或者
register_chrdev(int major,char *name,struct *file_operations);//major不为0是静态
②动态
register_chrdev(int major,char *name,struct *file_operations);//major为0是动态
或者
alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
4.创建好后可以在/proc/devices下看到设备名和设备编号
5.注意不管静态还是动态在移除驱动时都需要删除设备号
void unregister_chrdev_region(dev_t first,unsigned int count);
(三)设备文件
1.设备文件是具体设备在文件系统中的映射,使我们可以像对文件进行操作一样操作设备。应用程序通过操作设备文件然后经过驱动的代理进而操作具体设备。linux内核通过三个结构体变量实现上述操作。
(1)struct file,为了使用户空间的文件操作能够在内核空间中被保存,内核用一种和用户空间文件File不同的结构体file来保存文件参数,例如文件打开权限以及偏移量等等。
(2)struct file_operations,驱动程序为了使文件操作映射到设备操作,需要定义一些方法映射,这些方法就被保存在这个结构体里,例如open,read,write等等。
(3)struct inode,内核中会保存文件系统中每个文件的信息,这些信息就保存在inode结构体中,例如文件属性,是文件还是文件夹等等。
2.创建设备文件有两种办法,手动创建和自动创建。
(1)手动创建:
通过在shell中调用mknod命令,例如:
mknod /proc/testdev c 231 0
(2)自动创建:
class_create(struct module *owner, const char *name);
device_create(struct class *class, struct device *parent,dev_t devt, const char *fmt, ...);
3.注意设备文件也要驱动卸载时进行删除
命令行方式:
rm -fr /dev/testdev
内核:
device_destroy(struct class *class,dev_t devnum);
class_destroy(struct class *class);
(四)设备
1.内核为了保存设备的上述属性,它用另外一种结构体来保存,而且不同的结构体表示不同类型的设备,例如用struct cdev表示字符设备,对字符设备初始化如下:
cdev_init(struct cdev *dev,struct file_operations *fops);
cdev_add(struct cdev *dev,dev_t devnum,unsigned int count);
2.同样,此结构体在驱动卸载时也应该删除:
cdev_del(cdev *dev);
(五)代码
1.静态手动:
include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<asm/io.h>
MODULE_LICENSE("GPL");
#define DEVICE_NAME "led"
#define LED_MAJOR 231
static int __init led_init(void);
static void __exit led_exit(void);
static int led_open(struct inode *inode,struct file *file);
static long led_ioctl(struct file *file,unsigned int cmd,unsigned long arg);
static struct file_operations led_fops={
.owner=THIS_MODULE,
.open=led_open,
.unlocked_ioctl=led_ioctl,
};
static int led_open(struct inode *inode,struct file *file)
{
printk("open success");
return 0;
}
static long led_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
printk("led_ioctl cmd=[%d]",cmd);
}
static int __init led_init(void)
{
int ret=0;
ret=register_chrdev(LED_MAJOR,DEVICE_NAME,&led_fops);
if(ret<0)
{
printk("led cannot reg magor num :[%d] ret=[%d]",LED_MAJOR,ret);
return ret;
}
return ret;
}
static void __exit led_exit(void)
{
unregister_chrdev(LED_MAJOR,DEVICE_NAME);
}
module_init(led_init);
module_exit(led_exit);
2.动态创建设备号(register_chrdev(int major,char *name,struct *file_operations);//major为0是动态),不包括创建设备文件。
#include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<asm/io.h>
#include<linux/cdev.h>
MODULE_LICENSE("GPL");
#define DEVICE_NAME "test_dev1"
static unsigned int major_dev;
static int __init test_init(void);
static void __exit test_exit(void);
static int test_open(struct inode *inode,struct file *file);
static long test_ioctl(struct file *file,unsigned int cmd,unsigned long arg);
static int alloc_dev_num;
static dev_t num_dev;
struct cdev dev_c;
struct class *cdev_class;
static struct file_operations led_fops={
.owner=THIS_MODULE,
.open=test_open,
.unlocked_ioctl=test_ioctl,
//.ioctl=led_ioctl;
};
static int test_open(struct inode *inode,struct file *file)
{
printk("test__open");
return 0;
}
static long test_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
printk("test_ioctl cmd=[%d]",cmd);
return 0;
}
static int __init test_init(void)
{
int ret=0;
ret=register_chrdev(0,DEVICE_NAME,&led_fops);//成功是返回值即为设备号
if(ret<0)
{
printk("led cannot reg magor num :[%d] ret=[%d]",ret,ret);
return ret;
}
printk("led reg magor num success [%d]",ret);
major_dev=ret;
return ret;
}
static void __exit test_exit(void)
{
unregister_chrdev(major_dev,DEVICE_NAME);
printk("led unreg magor num :[%d] success",major_dev);
}
module_init(test_init);
module_exit(test_exit);
执行insmod有如下打印:
3.动态创建设备号,并且自动生成设备文件。
#include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<asm/io.h>
#include<linux/cdev.h>
MODULE_LICENSE("GPL");
#define DEVICE_NAME "test_dev1"
static int __init test_init(void);
static void __exit test_exit(void);
static int test_open(struct inode *inode,struct file *file);
static long test_ioctl(struct file *file,unsigned int cmd,unsigned long arg);
static int alloc_dev_num;
static dev_t num_dev;
struct cdev dev_c;
struct class *cdev_class;
static struct file_operations led_fops={
.owner=THIS_MODULE,
.open=test_open,
.unlocked_ioctl=test_ioctl,
//.ioctl=led_ioctl;
};
static int test_open(struct inode *inode,struct file *file)
{
printk("test__open");
return 0;
}
static long test_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
printk("test_ioctl cmd=[%d]",cmd);
return 0;
}
static int __init test_init(void)
{
int ret;
ret=alloc_chrdev_region(&num_dev,0,1,DEVICE_NAME);
alloc_dev_num=MAJOR(num_dev);
printk("alloc dev num=[%d] ret=[%d]\n",alloc_dev_num,ret);
cdev_init(&dev_c,&led_fops);
printk("cdev_init!");
ret=cdev_add(&dev_c,num_dev,1);
printk("cdev_add ret=[%d]",ret);
cdev_class=class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(cdev_class))
{
printk("class_create error!");
}
printk("class_create success!");
device_create(cdev_class,NULL,num_dev,0,DEVICE_NAME);
return ret;
}
static void __exit test_exit(void)
{
printk("test_exit");
device_destroy(cdev_class,num_dev);
class_destroy(cdev_class);
cdev_del(&dev_c);
unregister_chrdev_region(num_dev,1);
}
module_init(test_init);
module_exit(test_exit);
insmod后有如下输出
并且设备文件如下: