/*Linux字符设备驱动源代码scdd.c*/
#include <linux/init.h> /*模块头文件*/
#include <linux/module.h>
#include <linux/types.h> /*dev_t头文件*/
#include <linux/kdev_t.h> /*MAJOR和MINOR宏头文件*/
#include <linux/fs.h> /*register_chrdev_region等函数
file_operations结构体*/
#include <linux/cdev.h> /*struct cdev结构体*/
#include <asm/uaccess.h> /*copy_to_user函数*/
#define DEVICE_NAME "scdd" /*定义设备名*/
#define DEVICE_MAJOR 250
struct cdev my_cdev;
int scdd_open(struct inode *inode,structfile *filp)
{
return0;
}
int scdd_close(struct inode *inode,structfile *filp)
{
return0;
}
ssize_t scdd_read(struct file *filp,char__user *buff,size_t size,loff_t *offp)
{
intleft;
chardata=1;
for(left=size;left>0;left--)
{
/*拷贝数据到用户空间*/
copy_to_user(buff,&data,1);
buff++;
}
returnsize;
}
ssize_t scdd_write(struct file *filp,char__user *buff,size_t size,loff_t *offp)
{
return0;
}
/*file_operations结构体*/
struct file_operations scdd_fops={
.owner=THIS_MODULE,
.read=scdd_read,
.write=scdd_write,
.open=scdd_open,
.release=scdd_close,
};
static int __init scdd_init(void)
{ /*模块初始化函数*/
intsmajor;
smajor=DEVICE_MAJOR;
dev_tdev_n=MKDEV(smajor,0);
/*申请设备号*/
if(!register_chrdev_region(dev_n,1,DEVICE_NAME))
{ /*静态申请*/
printk("registersuccess\n");
}else
{
gotoregister_error;
}
/*else
{ /*动态申请*/
/*alloc_chrdev_region(&dev_n,0,1,DEVICE_NAME);
smajor=MAJOR(dev_n);
}*/
/*初始化cdev结构体*/
cdev_init(&my_cdev,&scdd_fops);
my_cdev.owner=THIS_MODULE;
my_cdev.ops=&scdd_fops;
/*注册字符设备*/
cdev_add(&my_cdev,dev_n,1);
return0;
register_error:
unregister_chrdev_region(MKDEV(DEVICE_MAJOR,0),1);
return0;
}
static void __exit scdd_exit(void)
{ /*模块卸载函数*/
cdev_del(&my_cdev);
unregister_chrdev_region(MKDEV(DEVICE_MAJOR,0),1);
}
module_init(scdd_init);
module_exit(scdd_exit);
MODULE_LICENSE("Dual BSD/GPL");
这个程序只是简单演示字符注册的一个完整过程,并不带有复杂的操作,调用read时向用户空间写全1
要点:
1.设备号,主设备号用来标识设备所对应的驱动程序,同一个驱动程序可以对应多个设备,次设备号就是用来区分采用同一个驱动程序的不同设备文件。
内核中采用dev_t来描述设备号,其实dev_t实质为unsigned int类型,其中高12位为主设备号,低20位为次设备号,其定义在linux/types.h中
从dev_t中分解出主设备号和次设备号
MAJOR(dev_t dev)
MINOR(dev_t dev)
将主设备号和次设备号转换成dev_t类型
MKDEV(intmajor,int minor)
2.申请设备号
静态申请,函数定义在linux/fs.h中
int register_chrdev_region(dev_t from,unsigned count,const char *name)
from是申请设备号起始值,from次设备号经常被置成0,count是所请求连续设备号个数,name是该设备号范围关联的名称,它将出现在/proc/device和sysfs中。
分配成功返回0,错误情况下,将返回一个负的错误码,并且不能使用所请求的设备号。
动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
申请的设备号将保存在dev中,baseminor为所申请的第一个次设备号
无论哪种方式申请设备号,不使用时要释放设备号
void unregister_chrdev_region(dev_t first,unsigned int count)
3.三个重要的结构
struct file_operations
struct file
struct inode
struct file_operations一个函数指针集合,定义能在设备上进行的操作,常用的有:
int (*open)(structinode *inode,stuuct file *filep)
void (*release)(structinode *inode,struct file *filep)
ssize_t(*read)(struct file *filep,char __user *buff,size_t size,loff_t *offp)
ssize_t(*write)(struct file *filep,char __user *buff,size_t seze,loff_t * offp)
int(*ioctl)(struct inode *inode,struct file *filep,unsigned int cmd,unsigned longarg) off_t(*llseek)(struct file *filp,loff_t,int)
struct file表示一个打开的文件,一个文件被打开十次,则有10个structfile结构
struct file结构成员有:
mode_t f_mode 文件模式,例如:可读可写
lofft_t f_pos 当前读/写位置
struct file_operations *f_op 与文件相关操作,执行open操作时对这个指针赋值
void *private_data在open时置为NULL,用于跨系统调用时保存非常有用的资源
struct inode用来记录文件的物理上的信息,一个文件被打开10次,但只有一个structinode结构,struct inode结构成员如下:
dev_t i_rdev 用来保存设备号
struct cdev*i_cdev 指向字符设备结构struct cdev的指针
4.字符设备的注册
内核使用struct cdev结构表示字符设备,定义在linux/cdev.h中。
初始化cdev结构体有两种方式
静态:
struct cdev my_cdev;
cdev_init(&my_cdev,&fops); my_cdev.owner = THIS_MODULE;
动态(就是通过kmalloc去申请cdev结构):
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops= &my_fops;
my_cdev.owner= THIS_MODULE;
注册
int cdev_add(struct cdev *dev,dev_t num,unsigned int count)
num是设备号,count经常取1
注销
void cdev_del(struct cdev *dev)
早起比较经典的字符设备驱动注册和注销方法
注册
int register_chrdev(unsigned int major,const char *name,struct file_operations*fops)
注销
int unregister_chrdev(unsigned int major,const char *name)