第三章字符设备驱动程序
1. 对字符设备的访问是通过文件系统内的设备名称的,那些名称成为特殊文件、设备文件,或者简单称之为文件系统树的节点,他们通常存在在/dev/下面,字符设备是ls 前面 是‘c’的表示字符设备,b的表示块设备。如下图:
ls 查看字符设备文件的时候,在时间前面2012-03-10 的200是主设备号、0代表次设备号 。调用mknod/dev/leok c 200 0 这里的200 ,0 就是主次设备号。一般主设备号对应一个驱动程序,但是现在现在的linux内核允许多个驱动程序共享主设备号。
次设备号作用是内核来确定设备文件所指的设备。
2. 在内核中用dev_t这个类型来表示主次设备号,在内核的2.6.0版本中,dev_t是一个32位的数,前12为代表主设备号,后20位代表次设备号。虽然2.6.0中这个定义的,但是我们不能对dev_t这个结构做任何假设,可能以后的版本会变化,获取主设备号和次设备号通过
MAJOR(dev_t dev)
MINOR(dev_t dev)这两个宏来获得是最安全的。
在<linux/kdev_t.h>中有定义。
如下代码是Description: Ubuntu 11.04(Linux leok 2.6.38-8-generic)
#ifndef _LINUX_KDEV_T_H
#define _LINUX_KDEV_T_H
/*
Some programs want their definitions of MAJOR and MINOR and MKDEV
from the kernel sources. These must be the externally visible ones.
*/
#define MAJOR(dev) ((dev)>>8) //此时的主设备号是前24位
#define MINOR(dev) ((dev) & 0xff) //次设备是低8位
#define MKDEV(ma,mi) ((ma)<<8 | (mi)) //构造dev_t ma(主) mi(次)
#endif
3
注册字符设备函数
/*注册一定范围的字符设备函数
* @param dev 注册的主次设备号,需要和字符设备文件关联
* @param count 请求连续编号字符设备的个数(次设备号开始)
* @param name 是请求该字符设备的名称
* return 0 表示注册成功
* return -1 表示注册失败
*/
int register_chrdev_region(dev_t dev, unsigned int count, char* name)
这个函数是在已知字符设备的编号的情况下调用的
如果dev让内核来分配的话,就调用下面的函数:
/* dev_t是由系统来分配的注册字符设备调用
* @param dev [out] 注册成功会把字符设备的主次设备编号返回
* @param firstminor 第一个使用的被请求的第一个次设备编号
* @param count 请求个数
* @param name 是请求的字符设备名称
* return 0 表示请求成功
* return -1 表示请求失败
*/
int alloc_chrdev_region(dev_t *dev, unsigned firstminor, unsigned count, char* name)
释放字符设备函数
/*释放字符设备占用资源函数
* @param dev 释放的设备主次设备号
* @param count 释放的个数
*/
void unregister_chrdev_region(dev_t dev, unsigned int count)
由于动态分配主次设备号,无法一直保持主次设备号不变(由内核确定主次设备号),就会增加了应用层的复杂性。但是这样做 是你编写的驱动程序适用性更强。通过遍历
4 file_operations结构体是定义在<linux/fs.h>中定义的,其中包含了一组函数指针。每个打开的文件和一组函数关联,这些函数主要用来实现系统调用,像read、write。其中的某些函数的参数有__user字段,代表的是用户空间的地址。对于内核是不能使用的(内存是分页的)。
file_operation成员: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*, char __user*, size_t, loff_t) //写设备
unsigned int (*poll)(struct file*, structpoll_table_struct*)
poll方法是poll epool select这三个系统调用的后端实现。这三个系统调用可用来查询某个或者多个文件描述符上的读取或写入是否可能,并且也会向内核提供将调用进程置于休眠状态知道I/O 变成可能时的信息
int (*ioctl)(struct inode*, structfile*, unsigned int, unsigned long)
ioctl提供一种执行设备特定命令的方法
int (*open)(struct inode*, structfile*)打开设备
int (*release)(struct inode*,struct file*) //file结构释放的时候
以上仅仅是file_operation 结构体的部分成员,还有一些可参考<linux 设备驱动程序>.
5. file结构是一个内核结构,而FILE是一个c库中定义而且不会出现在内核态中
file代表一个打开的文件(它不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file结构)。它由内核在open的时候创建,并穿钉给在该文件上进行操作的所有函数,直到最后的close函数,在文件的所有实例都被关闭之后,内核会释放这个数据结构。
struct file中的重要成员:
mode_t f_mode 文件模式。它通过FMODE_READ和FMODE_WRITE位来表示文件是否可读或者可写。
loff_t f_pos 当前读写位置
unsigned int f_flags 文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC
struct file_operation *f_op 上面说明了这个结构体。内核在执行open操作时对这个指针赋值。以后处理与这个文件描述符相关的操作时,就会调用内核态的这个结构体中的成员,比如read、write
以上也是file结构的部分成员。如果想了解更多查阅<linux 设备驱动程序>6. inode 结构 内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符,对于单个文件,可能会有许多个表示打开的文件描述符的file结构,但他们都指向单个inode结构,
inode结构中包含了大量有关的文件信息,作为常规,只有下面两个字段对编写驱动程序代码有用
dev_t i_rdev 真实设备编号
struct cdev *i_cdev表示字符设备的内核内部结构
三个数据结构之间的关系如下图
字符设备代码:可以使用
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#define READ_CONTENT "Hi welcome to linux kernel and you will be hacker. if you like doing it and just do it"
#define WRITE_CONTENT "I will miss you ,love you ,live with you .see you later"
chr_open(struct inode* node, struct file* filp)
{
printk("this is leok chacter device STATE:open");
return 0;
}
int chr_release(struct inode* node, struct file* filp)
{
printk("this is leok chacter device STATE:close");
return 0;
}
ssize_t chr_read(struct file* filp, char __user* buff, size_t count, loff_t* offp)
{
if(count > strlen(READ_CONTENT))
{
count = strlen(READ_CONTENT);
}
if(copy_to_user(buff, READ_CONTENT, count))
{
printk("copy_from_user failed");
return -1;
}
return count;
}
ssize_t chr_write(struct file* filp, char __user* buff, size_t count, loff_t* offp)
{
char *p_read =(char*) kmalloc(count, GFP_KERNEL);
if(p_read == NULL)
{
printk("kmalloc failed");
return -1;
}
if(copy_from_user(p_read, buff, count))
{
printk("copy_from_user failed");
return -1;
}
printk("user pass conten is %s", p_read);
kfree(p_read);
return count;
}
struct file_operations chr_dev = {
.owner = THIS_MODULE,
.read = chr_read,
.write = chr_write,
.open = chr_open,
.release = chr_release,
};
void __init chr_dev_init(void)
{
dev_t dev;
dev = MKDEV(200, 0);
if(register_chrdev(200, "leok", &chr_dev) != -1)
{
printk("register chr_dev success\n");
}
else
{
printk("register chr_dev failed\n");
}
printk("chr_dev initialize and acccess\n");
return ;
}
static void chr_dev_exit(void)
{
dev_t dev = MKDEV(200, 0);
unregister_chrdev(200, "leok");
printk("ungister success and drives will exit\n");
return;
}
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("leok/GPL");
期间出现了一些问题查找的原因也贴上
这个程序还没用到最新的注册函数,以后会修改
error: 编译报错warning: function declaration isn't a prototype
就得把参数为空的函数写成(void)就ok了
ISO C90 forbids mixed declarations and code
主要是变量先得声明,才能调用函数