linux设备驱动程序——字符设备驱动程序
2009-10-27 14:43
204 查看
在Linux操作系统下有3类主要的设备文件类型:块设备、字符设备和网络设备。这种分类方法可以将控制输入/输出设备的驱动程序与其他操作系统软件分离开来。字符设备是指存取时没有缓存的设备。典型的字符设备包括鼠标、键盘、串行口等。
字符设备与块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般紧接着发生。块设备则不然,它利用一块系统内存作为缓冲区,若用户进程对设备的请求能满足用户的要求,就返回请求的数据;否则,就调用请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,以免耗费过多的CPU时间用来等待。网络设备可以通过BSD套接口访问数据。
一.主设备号和次设备号
主设备号标识设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。我们可以通过次设备号获得一个指向内核设备的直接指针,也可将次设备号当作设备本地数组的索引,不管用哪种方式,除了知道次设备号用来指向驱动程序所实现的设备之外,内核本身基本上不关心关于次设备号的任何其他消息。
◎设备编号的内部表达
内核用dev_t类型(<linux/types.h>)来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20位表示次设备号。
在实际使用中,是通过<linux/kdev_t.h>中定义的宏来转换格式。
(dev_t)-->主设备号、次设备号 MAJOR(dev_t dev) MINOR(dev_t dev)
主设备号、次设备号-->(dev_t) MKDEV(int major,int minor)
◎分配和释放设备编号
建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在<linux/fs.h>中声明:
int register_chrdev_region(dev_t first, unsigned int count,char *name); //指定设备编号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name); //动态生成设备编号
void unregister_chrdev_region(dev_t first, unsigned int count); //释放设备编号
分配之设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。
◎动态分配主设备号
某些主设备号已经静态地分配给了大部分公用设备。在内核源码树的Documentation/device.txt文件中可以找到这些设备的列表。
一旦驱动程序被广泛使用,随机选定的主设备号可能造成冲突和麻烦。
强烈推荐你不要随便选择一个一个当前不用的设备号做为主设备号,而使用动态分配机制获取你的主设备号。
动态分配的缺点是,由于分配给你的主设备号不能保证总是一样的,无法事先创建设备节点。然而这不是什么问题,这是因为一旦分配了设备号,你就可以从/proc/devices读到。为了加载一个设备驱动程序,对insmod的调用被替换为一个简单的脚本,它通过/proc/devices获得新分配的主设备号,并创建节点。
分配主设备号的最佳方式:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。
以下是在scull.c中用来获取主设备好的代码:
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d/n", scull_major);
return result;
}
在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出在/proc/devices和sysfs中。
看到这里,就可以理解为什么mdev和udev可以动态、自动地生成当前系统需要的设备文件。udev就是通过读取sysfs下的信息来识别硬件设备的.
二、一些重要的数据结构
文件操作file_operations
<linux/fs.h>中定义file_operations。
__user表明指针是一个用户空间地址,因此不能被直接引用。
ssize_t (*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t *); 初始化设备上的异步写入操作。
unsigned int (*poll) (struct file *, struct poll_table_struct *);是poll、epoll和select这三个系统调用的后端实现。可用来查询某个或多个文件描述符上的读取或写入是否会被阻塞。
int (*mmap) (struct file *, struct vm_area_struct *);用于请求将设备内存映射到进程地址空间。
int (*fsync) (struct file *, struct dentry *, int);用户调用它来刷新待处理的数据。
int (*fasync) (int, struct file *, int);用来通知设备其FASYNC标志发生了变化。
file结构
struct file是一个内核结构,不会出现在用户程序中
inode结构
内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。对单个文件,可能会有许多个表示打开的文件描述符的file结构,但它们都指向单个inode结构。
dev_t i_rdev:对表示设备文件的inode结构,该字段包含了真正的设备编号。
struct cdev *i_cdev;struct cdev表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);用来从一个inode中获得主设备号和次设备号
三、字符设备的注册
内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个struct cdev。代码应包含<linux/cdev.h>,它定义了struct cdev以及与其相关的一些辅助函数。
注册一个独立的cdev设备的基本过程如下:
1、为struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)
struct cdev *my_cdev = cdev_alloc();
2、初始化struct cdev
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
3、初始化cdev.owner
cdev.owner = THIS_MODULE;
4、cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
从系统中移除一个字符设备:void cdev_del(struct cdev *p)
附:
概括的说,字符设备驱动主要要做三件事:
1、定义一个结构体static struct file_operations变量,其内定义一些设备的打开、关闭、读、写、控制函数;
2、在结构体外分别实现结构体中定义的这些函数;
3、向内核中注册或删除驱动模块。
字符设备提供给应用程序流控制接口有:open/close/read/write/ioctl,添加一个字符设备驱动程序,实际上是给上述操作添加对应的代码,Linux对这些操作统一做了抽象 struct file_operations
file_operations结构体的例子如下
static struct file_operations myDriver_fops = {
owner: THIS_MODULE,
write: myDriver_write,
read: myDriver_read,
ioctl: myDriver_ioctl,
open: myDriver_open,
release: myDriver_release,
};
该结构体规定了驱动程序向应用程序提供的操作接口:
实现write操作
从应用程序接收数据送到硬件。例:
static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
size_t fill_size = count;
PRINTK("myDriver write called!/n");
PRINTK("/tcount=%d, pos=%d/n", count, (int)*f_pos);
if(*f_pos >= sizeof(myDriver_Buffer))
{
PRINTK("[myDriver write]Buffer Overlap/n");
*f_pos = sizeof(myDriver_Buffer);
return 0;
}
if((count + *f_pos) > sizeof(myDriver_Buffer))
{
PRINTK("count + f_pos > sizeof buffer/n");
fill_size = sizeof(myDriver_Buffer) - *f_pos;
}
copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size);
*f_pos += fill_size;
return fill_size;
}
其中的关键函数
u_long copy_from_user(void *to, const void *from, u_long len);
把用户态的数据拷到内核态,实现数据的传送。
实现read操作
从硬件读取数据并交给应用程序。例:
static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
size_t read_size = count;
PRINTK("myDriver read called!/n");
PRINTK("/tcount=%d, pos=%d/n", count, (int)*f_pos);
if(*f_pos >= sizeof(myDriver_Buffer))
{
PRINTK("[myDriver read]Buffer Overlap/n");
*f_pos = sizeof(myDriver_Buffer);
return 0;
}
if((count + *f_pos) > sizeof(myDriver_Buffer))
{
PRINTK("count + f_pos > sizeof buffer/n");
read_size = sizeof(myDriver_Buffer) - *f_pos;
}
copy_to_user(buf, &myDriver_Buffer[*f_pos], read_size);
*f_pos += read_size;
return read_size;
}
其中的关键函数
u_long copy_to_user(void * to, const void *from, u_long len);
该函数实现把内核态的数据拷到用户态下。
实现ioctl操作
为应用程序提供对硬件行为的控制。例:
static int myDriver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
PRINTK("myDriver ioctl called(%d)!/n", cmd);
if(_IOC_TYPE(cmd) != TSTDRV_MAGIC)
{
return -ENOTTY;
}
if(_IOC_NR(cmd) >= TSTDRV_MAXNR)
{
return -ENOTTY;
}
switch(cmd)
{
case MYDRV_IOCTL0:
PRINTK("IOCTRL 0 called(0x%lx)!/n", arg);
break;
case MYDRV_IOCTL1:
PRINTK("IOCTRL 1 called(0x%lx)!/n", arg);
break;
case MYDRV_IOCTL2:
PRINTK("IOCTRL 2 called(0x%lx)!/n", arg);
break;
case MYDRV_IOCTL3:
PRINTK("IOCTRL 3 called(0x%lx)!/n", arg);
break;
}
return 0;
}
实现open操作
当应用程序打开设备时对设备进行初始化,使用MOD_INC_USE_COUNT增加驱动程序的使用次数。例:
static int myDriver_open(struct inode *inode, struct file *filp)
{
MOD_INC_USE_COUNT;
PRINTK("myDriver open called!/n");
return 0;
}
实现release操作
当应用程序关闭设备时处理设备的关闭操作。使用MOD_DEC_USE_COUNT增加驱动程序的使用次数。例:
static int myDriver_release(struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
PRINTK("myDriver release called!/n");
return 0;
}
驱动程序初始化函数
Linux在加载内核模块时会调用初始化函数,初始化驱动程序本身使用register_chrdev向内核注册驱动程序,该函数的第三个指向包含有驱动程序接口函数信息的file_operations结构体。
例:
/* Module Init & Exit function */
#ifdef CONFIG_DEVFS_FS
devfs_handle_t devfs_myDriver_dir;
devfs_handle_t devfs_myDriver_raw;
#endif
static int __init myModule_init(void)
{
/* Module init code */
PRINTK("myModule_init/n");
/* Driver register */
myDriver_Major = register_chrdev(0, DRIVER_NAME, &myDriver_fops);
if(myDriver_Major < 0)
{
PRINTK("register char device fail!/n");
return myDriver_Major;
}
PRINTK("register myDriver OK! Major = %d/n", myDriver_Major);
#ifdef CONFIG_DEVFS_FS
devfs_myDriver_dir = devfs_mk_dir(NULL, "myDriver", NULL);
devfs_myDriver_raw = devfs_register(devfs_myDriver_dir, "raw0", DEVFS_FL_DEFAULT, myDriver_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &myDriver_fops, NULL);
PRINTK("add dev file to devfs OK!/n");
#endif
return 0;
}
其中,关键函数
module_init()
向内核声明当前模块的初始化函数
驱动程序退出函数
Linux在卸载内核模块时会调用退出函数释放驱动程序使用的资源,使用unregister_chrdev从内核中卸载驱动程序。将驱动程序模块注册到内核,内核需要知道模块的初始化函数和退出函数,才能将模块放入自己的管理队列中。
例:
static void __exit myModule_exit(void)
{
/* Module exit code */
PRINTK("myModule_exit/n");
/* Driver unregister */
if(myDriver_Major > 0)
{
#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_myDriver_raw);
devfs_unregister(devfs_myDriver_dir);
#endif
unregister_chrdev(myDriver_Major, DRIVER_NAME);
}
return;
}
其中,关键函数
module_exit()
向内核声明当前模块的退出函数。
关于devfs的操作
在devfs中建立一个目录(/dev下)
devfs_handle_t devfs_mk_dir (devfs_handle_t dir, const char *name, void *info);
在devfs中注册一个设备文件节点
devfs_handle_t devfs_register(devfs_handle_t dir, const char *name,
unsigned int flags,
unsigned int major, unsigned int minor,
umode_t mode, void *ops, void *info);
在devfs中删除一个设备文件节点
void devfs_unregister(devfs_handle_t de);
加载驱动程序
在终端下,输入以下命令可以对模块进行相关的操作。
insmod 内核模块文件名
rmmod 内核模块文件名
lsmod 列举出当前全部的加载模块文件名
建立设备文件
mknod 文件路径 c [主设备号] [从设备号]
应用程序接口函数
可以使用标准C的文件操作函数来完成。
应用程序接口函数:
int open(const char *path, int oflag,…);
打开名为path的文件或设备
成功打开后返回文件句柄
常用oflag:O_RDONLY, O_WRONLY, O_RDWR
int close(int fd);
关闭之前被打开的文件或设备
成功关闭返回0,否则返回错误代号
ssize_t read(int fd, void *buffer, size_t count);
从已经打开的文件或设备中读取数据
buffer表示应用程序缓冲区
count表示应用程序希望读取的数据长度
成功读取后返回读取的字节数,否则返回-1
ssize_t write(int fd, void *buffer, size_t count);
向已经打开的文件或设备中写入数据
buffer表示应用程序缓冲区
count表示应用程序希望写入的数据长度
成功写入后返回写入的字节数,否则返回-1
int ioctl(int fd, unsigned long int cmd,…);
向驱动程序发送控制命令
cmd需是唯一值
type:又称幻数,8bit,一般表示cmd所属模块
number:cmd序号,8bit,一般表示实际的命令
direction:数据传输方向,2bit
size:数据大小,位数与体系结构有关(ARM:12bit)
sizeof(int)-(sizeof(type)+sizeof(number)+sizeof(direction))
使用_IO宏可快速合成cmd:_IO(MAGIC, num)
字符设备与块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般紧接着发生。块设备则不然,它利用一块系统内存作为缓冲区,若用户进程对设备的请求能满足用户的要求,就返回请求的数据;否则,就调用请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,以免耗费过多的CPU时间用来等待。网络设备可以通过BSD套接口访问数据。
一.主设备号和次设备号
主设备号标识设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。我们可以通过次设备号获得一个指向内核设备的直接指针,也可将次设备号当作设备本地数组的索引,不管用哪种方式,除了知道次设备号用来指向驱动程序所实现的设备之外,内核本身基本上不关心关于次设备号的任何其他消息。
◎设备编号的内部表达
内核用dev_t类型(<linux/types.h>)来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20位表示次设备号。
在实际使用中,是通过<linux/kdev_t.h>中定义的宏来转换格式。
(dev_t)-->主设备号、次设备号 MAJOR(dev_t dev) MINOR(dev_t dev)
主设备号、次设备号-->(dev_t) MKDEV(int major,int minor)
◎分配和释放设备编号
建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在<linux/fs.h>中声明:
int register_chrdev_region(dev_t first, unsigned int count,char *name); //指定设备编号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name); //动态生成设备编号
void unregister_chrdev_region(dev_t first, unsigned int count); //释放设备编号
分配之设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。
◎动态分配主设备号
某些主设备号已经静态地分配给了大部分公用设备。在内核源码树的Documentation/device.txt文件中可以找到这些设备的列表。
一旦驱动程序被广泛使用,随机选定的主设备号可能造成冲突和麻烦。
强烈推荐你不要随便选择一个一个当前不用的设备号做为主设备号,而使用动态分配机制获取你的主设备号。
动态分配的缺点是,由于分配给你的主设备号不能保证总是一样的,无法事先创建设备节点。然而这不是什么问题,这是因为一旦分配了设备号,你就可以从/proc/devices读到。为了加载一个设备驱动程序,对insmod的调用被替换为一个简单的脚本,它通过/proc/devices获得新分配的主设备号,并创建节点。
分配主设备号的最佳方式:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。
以下是在scull.c中用来获取主设备好的代码:
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d/n", scull_major);
return result;
}
在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出在/proc/devices和sysfs中。
看到这里,就可以理解为什么mdev和udev可以动态、自动地生成当前系统需要的设备文件。udev就是通过读取sysfs下的信息来识别硬件设备的.
二、一些重要的数据结构
文件操作file_operations
<linux/fs.h>中定义file_operations。
__user表明指针是一个用户空间地址,因此不能被直接引用。
ssize_t (*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t *); 初始化设备上的异步写入操作。
unsigned int (*poll) (struct file *, struct poll_table_struct *);是poll、epoll和select这三个系统调用的后端实现。可用来查询某个或多个文件描述符上的读取或写入是否会被阻塞。
int (*mmap) (struct file *, struct vm_area_struct *);用于请求将设备内存映射到进程地址空间。
int (*fsync) (struct file *, struct dentry *, int);用户调用它来刷新待处理的数据。
int (*fasync) (int, struct file *, int);用来通知设备其FASYNC标志发生了变化。
file结构
struct file是一个内核结构,不会出现在用户程序中
inode结构
内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。对单个文件,可能会有许多个表示打开的文件描述符的file结构,但它们都指向单个inode结构。
dev_t i_rdev:对表示设备文件的inode结构,该字段包含了真正的设备编号。
struct cdev *i_cdev;struct cdev表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);用来从一个inode中获得主设备号和次设备号
三、字符设备的注册
内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个struct cdev。代码应包含<linux/cdev.h>,它定义了struct cdev以及与其相关的一些辅助函数。
注册一个独立的cdev设备的基本过程如下:
1、为struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)
struct cdev *my_cdev = cdev_alloc();
2、初始化struct cdev
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
3、初始化cdev.owner
cdev.owner = THIS_MODULE;
4、cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
从系统中移除一个字符设备:void cdev_del(struct cdev *p)
附:
概括的说,字符设备驱动主要要做三件事:
1、定义一个结构体static struct file_operations变量,其内定义一些设备的打开、关闭、读、写、控制函数;
2、在结构体外分别实现结构体中定义的这些函数;
3、向内核中注册或删除驱动模块。
字符设备提供给应用程序流控制接口有:open/close/read/write/ioctl,添加一个字符设备驱动程序,实际上是给上述操作添加对应的代码,Linux对这些操作统一做了抽象 struct file_operations
file_operations结构体的例子如下
static struct file_operations myDriver_fops = {
owner: THIS_MODULE,
write: myDriver_write,
read: myDriver_read,
ioctl: myDriver_ioctl,
open: myDriver_open,
release: myDriver_release,
};
该结构体规定了驱动程序向应用程序提供的操作接口:
实现write操作
从应用程序接收数据送到硬件。例:
static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
size_t fill_size = count;
PRINTK("myDriver write called!/n");
PRINTK("/tcount=%d, pos=%d/n", count, (int)*f_pos);
if(*f_pos >= sizeof(myDriver_Buffer))
{
PRINTK("[myDriver write]Buffer Overlap/n");
*f_pos = sizeof(myDriver_Buffer);
return 0;
}
if((count + *f_pos) > sizeof(myDriver_Buffer))
{
PRINTK("count + f_pos > sizeof buffer/n");
fill_size = sizeof(myDriver_Buffer) - *f_pos;
}
copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size);
*f_pos += fill_size;
return fill_size;
}
其中的关键函数
u_long copy_from_user(void *to, const void *from, u_long len);
把用户态的数据拷到内核态,实现数据的传送。
实现read操作
从硬件读取数据并交给应用程序。例:
static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
size_t read_size = count;
PRINTK("myDriver read called!/n");
PRINTK("/tcount=%d, pos=%d/n", count, (int)*f_pos);
if(*f_pos >= sizeof(myDriver_Buffer))
{
PRINTK("[myDriver read]Buffer Overlap/n");
*f_pos = sizeof(myDriver_Buffer);
return 0;
}
if((count + *f_pos) > sizeof(myDriver_Buffer))
{
PRINTK("count + f_pos > sizeof buffer/n");
read_size = sizeof(myDriver_Buffer) - *f_pos;
}
copy_to_user(buf, &myDriver_Buffer[*f_pos], read_size);
*f_pos += read_size;
return read_size;
}
其中的关键函数
u_long copy_to_user(void * to, const void *from, u_long len);
该函数实现把内核态的数据拷到用户态下。
实现ioctl操作
为应用程序提供对硬件行为的控制。例:
static int myDriver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
PRINTK("myDriver ioctl called(%d)!/n", cmd);
if(_IOC_TYPE(cmd) != TSTDRV_MAGIC)
{
return -ENOTTY;
}
if(_IOC_NR(cmd) >= TSTDRV_MAXNR)
{
return -ENOTTY;
}
switch(cmd)
{
case MYDRV_IOCTL0:
PRINTK("IOCTRL 0 called(0x%lx)!/n", arg);
break;
case MYDRV_IOCTL1:
PRINTK("IOCTRL 1 called(0x%lx)!/n", arg);
break;
case MYDRV_IOCTL2:
PRINTK("IOCTRL 2 called(0x%lx)!/n", arg);
break;
case MYDRV_IOCTL3:
PRINTK("IOCTRL 3 called(0x%lx)!/n", arg);
break;
}
return 0;
}
实现open操作
当应用程序打开设备时对设备进行初始化,使用MOD_INC_USE_COUNT增加驱动程序的使用次数。例:
static int myDriver_open(struct inode *inode, struct file *filp)
{
MOD_INC_USE_COUNT;
PRINTK("myDriver open called!/n");
return 0;
}
实现release操作
当应用程序关闭设备时处理设备的关闭操作。使用MOD_DEC_USE_COUNT增加驱动程序的使用次数。例:
static int myDriver_release(struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
PRINTK("myDriver release called!/n");
return 0;
}
驱动程序初始化函数
Linux在加载内核模块时会调用初始化函数,初始化驱动程序本身使用register_chrdev向内核注册驱动程序,该函数的第三个指向包含有驱动程序接口函数信息的file_operations结构体。
例:
/* Module Init & Exit function */
#ifdef CONFIG_DEVFS_FS
devfs_handle_t devfs_myDriver_dir;
devfs_handle_t devfs_myDriver_raw;
#endif
static int __init myModule_init(void)
{
/* Module init code */
PRINTK("myModule_init/n");
/* Driver register */
myDriver_Major = register_chrdev(0, DRIVER_NAME, &myDriver_fops);
if(myDriver_Major < 0)
{
PRINTK("register char device fail!/n");
return myDriver_Major;
}
PRINTK("register myDriver OK! Major = %d/n", myDriver_Major);
#ifdef CONFIG_DEVFS_FS
devfs_myDriver_dir = devfs_mk_dir(NULL, "myDriver", NULL);
devfs_myDriver_raw = devfs_register(devfs_myDriver_dir, "raw0", DEVFS_FL_DEFAULT, myDriver_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &myDriver_fops, NULL);
PRINTK("add dev file to devfs OK!/n");
#endif
return 0;
}
其中,关键函数
module_init()
向内核声明当前模块的初始化函数
驱动程序退出函数
Linux在卸载内核模块时会调用退出函数释放驱动程序使用的资源,使用unregister_chrdev从内核中卸载驱动程序。将驱动程序模块注册到内核,内核需要知道模块的初始化函数和退出函数,才能将模块放入自己的管理队列中。
例:
static void __exit myModule_exit(void)
{
/* Module exit code */
PRINTK("myModule_exit/n");
/* Driver unregister */
if(myDriver_Major > 0)
{
#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_myDriver_raw);
devfs_unregister(devfs_myDriver_dir);
#endif
unregister_chrdev(myDriver_Major, DRIVER_NAME);
}
return;
}
其中,关键函数
module_exit()
向内核声明当前模块的退出函数。
关于devfs的操作
在devfs中建立一个目录(/dev下)
devfs_handle_t devfs_mk_dir (devfs_handle_t dir, const char *name, void *info);
在devfs中注册一个设备文件节点
devfs_handle_t devfs_register(devfs_handle_t dir, const char *name,
unsigned int flags,
unsigned int major, unsigned int minor,
umode_t mode, void *ops, void *info);
在devfs中删除一个设备文件节点
void devfs_unregister(devfs_handle_t de);
加载驱动程序
在终端下,输入以下命令可以对模块进行相关的操作。
insmod 内核模块文件名
rmmod 内核模块文件名
lsmod 列举出当前全部的加载模块文件名
建立设备文件
mknod 文件路径 c [主设备号] [从设备号]
应用程序接口函数
可以使用标准C的文件操作函数来完成。
应用程序接口函数:
int open(const char *path, int oflag,…);
打开名为path的文件或设备
成功打开后返回文件句柄
常用oflag:O_RDONLY, O_WRONLY, O_RDWR
int close(int fd);
关闭之前被打开的文件或设备
成功关闭返回0,否则返回错误代号
ssize_t read(int fd, void *buffer, size_t count);
从已经打开的文件或设备中读取数据
buffer表示应用程序缓冲区
count表示应用程序希望读取的数据长度
成功读取后返回读取的字节数,否则返回-1
ssize_t write(int fd, void *buffer, size_t count);
向已经打开的文件或设备中写入数据
buffer表示应用程序缓冲区
count表示应用程序希望写入的数据长度
成功写入后返回写入的字节数,否则返回-1
int ioctl(int fd, unsigned long int cmd,…);
向驱动程序发送控制命令
cmd需是唯一值
type:又称幻数,8bit,一般表示cmd所属模块
number:cmd序号,8bit,一般表示实际的命令
direction:数据传输方向,2bit
size:数据大小,位数与体系结构有关(ARM:12bit)
sizeof(int)-(sizeof(type)+sizeof(number)+sizeof(direction))
使用_IO宏可快速合成cmd:_IO(MAGIC, num)
相关文章推荐
- Linux设备驱动程序第三版学习(9)- 高级字符驱动程序操作(续4) - llseek定位设备
- linux设备驱动程序第二版 字符设备驱动程序的扩展操作
- Linux设备驱动程序第三版学习(1)-字符设备驱动程序源码分析
- Linux设备驱动程序第三版学习(1)(2)-字符设备驱动程序源码分析
- Linux设备驱动程序——简单字符设备驱动程序
- LINUX设备驱动程序笔记(三)字符设备驱动程序
- Linux设备驱动程序系列(二) 字符设备驱动程序(2)
- 《Linux设备驱动程序》读书笔记:字符设备驱动程序(一)
- Linux设备驱动程序系列(二) 字符设备驱动程序(1)
- 《Linux设备驱动程序》——字符设备驱动程序
- Linux设备驱动程序第三版学习(9)- 高级字符驱动程序操作(续4) - llseek定位设备
- linux设备驱动程序第二版 字符设备驱动程序
- Linux设备驱动程序第三版学习(1)-字符设备驱动程序源码分析
- Linux设备驱动程序第三版学习(1)-字符设备驱动程序源码分析
- 嵌入式 字符设备驱动程序
- 字符设备驱动程序学习笔记二
- 字符设备驱动程序中重要的三个数据结构file_operations、inode、file
- 字符设备驱动程序之同步互斥阻塞
- Linux 2.6 字符设备驱动程序
- Linux 2.6 字符设备驱动程序