linux驱动学习--第十一天:第六章 Linux 字符设备驱动(一) 之 Linux 字符设备驱动结构

6.1.1 cdev 结构体
在 Linux 2.6 内核中使用cdev 结构体描述字符设备,cdev 结构体的定义如代码清单6.1 所示。
代码清单6.1 cdev 结构体
1 struct cdev
2 {
3 struct kobject kobj; /* 内嵌的kobject 对象 */
4 struct module *owner; /*所属模块*/
5 struct file_operations *ops; /*文件操作结构体*/
6 struct list_head list;
7 dev_t dev; /*设备号*/
8 unsigned int count;
9 };
cdev 结构体的dev_t 成员定义了设备号,为32 位,其中高12 位为主设备号,低20 位为次设备号。使
用下列宏可以从dev_t 获得主设备号和次设备号。
MAJOR(dev_t dev)
MINOR(dev_t dev)
而使用下列宏则可以通过主设备号和设备号生成dev_t。
MKDEV(int major, int minor)
cdev 结构体的另一个重要成员file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数。
Linux 2.6 内核提供了一组函数用于操作cdev 结构体,如下所示:
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
cdev_init()函数用于初始化cdev 的成员,并建立cdev 和file_operations 之间的连接,其源代码如代码
清单6.2 所示。
代码清单6.2 cdev_init()函数
1 void cdev_init(struct cdev *cdev, struct file_operations *fops)
2 {
3 memset(cdev, 0, sizeof *cdev);
4 INIT_LIST_HEAD(&cdev->list);
5 cdev->kobj.ktype = &ktype_cdev_default;
6 kobject_init(&cdev->kobj);
7 cdev->ops = fops; /*将传入的文件操作结构体指针赋值给cdev 的ops*/
8 }
cdev_alloc()函数用于动态申请一个cdev 内存,其源代码如代码清单6.3 所示。

代码清单6.3 cdev_alloc()函数
1 struct cdev *cdev_alloc(void)
2 {
3 struct cdev *p=kmalloc(sizeof(struct cdev),GFP_KERNEL); /*分配cdev 的内存*/
4 if (p) {
5 memset(p, 0, sizeof(struct cdev));
6 p->kobj.ktype = &ktype_cdev_dynamic;
7 INIT_LIST_HEAD(&p->list);
8 kobject_init(&p->kobj);
9 }
10 return p;
11 }
cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对
cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符
设备驱动模块卸载函数中。

6.1.2 分配和释放设备号
在 调 用 cdev_add() 函数向系统注册字符设备之前, 应首先调用register_chrdev_region() 或
alloc_chrdev_region()函数向系统申请设备号,这两个函数的原型如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
register_chrdev_region()函数用于已知起始设备的设备号的情况;而alloc_chrdev_region()用于设备号未
知,向系统动态申请未被占用的设备号的情况。函数调用成功之后,会把得到的设备号放入第一个参数dev
中。alloc_chrdev_region()与register_chrdev_region()对比的优点在于它会自动避开设备号重复的冲突。
相反地,在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放
原先申请的设备号,这个函数的原型如下:
void unregister_chrdev_region(dev_t from, unsigned count);
6.1.3 file_operations 结构体
file_operations 结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序
进行Linux 的open()、write()、read()、close()等系统调用时最终被调用。file_operations 结构体目前已经比
较庞大,它的定义如代码清单6.4 所示。
代码清单6.4 file_operations 结构体
1 struct file_operations
2 {
3 struct module *owner;
4 // 拥有该结构的模块的指针,一般为THIS_MODULES
5 loff_t(*llseek)(struct file *, loff_t, int);
6 // 用来修改文件当前的读写位置
7 ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*);
8 // 从设备中同步读取数据
9 ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);
10 // 初始化一个异步的读取操作
11 ssize_t(*write)(struct file *, const char _ _user *, size_t, loff_t*);
12 // 向设备发送数据
13 ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t);
14 // 初始化一个异步的写入操作
15 int(*readdir)(struct file *, void *, filldir_t);
16 // 仅用于读取目录,对于设备文件,该字段为 NULL
17 unsigned int(*poll)(struct file *, struct poll_table_struct*);
18 // 轮询函数,判断目前是否可以进行非阻塞的读取或写入
19 int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);
20 // 执行设备I/O 控制命令
21 long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
22 // 不使用BLK 文件系统,将使用此种函数指针代替ioctl
23 long(*compat_ioctl)(struct file *, unsigned int, unsigned long);
24 // 在64 位系统上,32 位的ioctl 调用将使用此函数指针代替

25 int(*mmap)(struct file *, struct vm_area_struct*);
26 // 用于请求将设备内存映射到进程地址空间
27 int(*open)(struct inode *, struct file*);
28 // 打开
29 int(*flush)(struct file*);
30 int(*release)(struct inode *, struct file*);
31 // 关闭
32 int(*synch)(struct file *, struct dentry *, int datasync);
33 // 刷新待处理的数据
34 int(*aio_fsync)(struct kiocb *, int datasync);
35 // 异步fsync
36 int(*fasync)(int, struct file *, int);
37 // 通知设备FASYNC 标志发生变化
38 int(*lock)(struct file *, int, struct file_lock*);
39 ssize_t(*readv)(struct file *, const struct iovec *, unsigned long, loff_t*);
40 ssize_t(*writev)(struct file *, const struct iovec *, unsigned long, loff_t*);
41 // readv 和writev:分散/聚集型的读写操作
42 ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void*);
43 // 通常为NULL
44 ssize_t(*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);
45 // 通常为NULL
46 unsigned long(*get_unmapped_area)(struct file *,unsigned long, unsigned long,
47 unsigned long, unsigned long);
48 // 在进程地址空间找到一个将底层设备中的内存段映射的位置
49 int(*check_flags)(int);
50 // 允许模块检查传递给fcntl(F_SETEL...)调用的标志
51 int(*dir_notify)(struct file *filp, unsigned long arg);
52 // 仅对文件系统有效,驱动程序不必实现
53 int(*flock)(struct file *, int, struct file_lock*);
54 };
下面对file_operations 结构体中的主要成员进行讲解。
llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。
write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行write()
系统调用时,将得到-EINVAL 返回值。
readdir()函数仅用于目录,设备节点不需要实现它。
ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,返回给调用程序一
个非负值。内核本身识别部分控制命令,而不必调用设备驱动中的ioctl()。如果设备不提供ioctl()函数,对
于内核不能识别的命令,用户进行ioctl()系统调用时将获得-EINVAL 返回值。
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行mmap()系统调用
时将获得-ENODEV 返回值。这个函数对于帧缓冲等设备特别有意义。
当用户空间调用 Linux API 函数open()打开设备文件时,设备驱动的open()函数最终被调用。驱动程序
可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与open()函数对应的是release()函数。
poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,用户空间进行select()
和poll()系统调用将引起进程的阻塞。
aio_read()和aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。设备实现这两个函数
后,用户空间可以对该设备文件描述符调用aio_read()、aio_write()等系统调用进行读写。
6.1.4 Linux 字符设备驱动的组成
在 Linux 系统中,字符设备驱动由如下几个部分组成。1.字符设备驱动模块加载与卸载函数
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 的注册,而在卸载函数中应实现设备号
的释放和cdev 的注销。
工程师通常习惯将设备定义为一个设备相关的结构体,其包含该设备所涉及的cdev、私有数据及信号量等信息。常见的设备结构体、模块加载和卸载函数形式如代码清单 6.5 所示。
代码清单6.5 字符设备驱动模块加载与卸载函数模板
1 //设备结构体
2 struct xxx_dev_t
3 {
4 struct cdev cdev;
5 ...
6 } xxx_dev;
7 //设备驱动模块加载函数
8 static int _ _init xxx_init(void)
9 {
10 ...
11 cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化cdev
12 xxx_dev.cdev.owner = THIS_MODULE;
13 //获取字符设备号
14 if (xxx_major)
15 {
16 register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
17 }
18 else
19 {
20 alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
21 }
22
23 ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备
24 ...
25 }
26 /*设备驱动模块卸载函数*/
27 static void _ _exit xxx_exit(void)
28 {
29 unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号
30 cdev_del(&xxx_dev.cdev); //注销设备
31 ...
32 }
2.字符设备驱动的file_operations 结构体中成员函数
file_operations 结构体中成员函数是字符设备驱动与内核的接口,是用户空间对Linux 进行系统调用最
终的落实者。大多数字符设备驱动会实现read()、write()和ioctl()函数,常见的字符设备驱动的这3 个函数的形式如代码清单6.6 所示。
代码清单6.6 字符设备驱动读、写、I/O 控制函数模板
1 /* 读设备*/
2 ssize_t xxx_read(struct file *filp, char _ _user *buf, size_t count,
3 loff_t*f_pos)
4 {
5 ...
6 copy_to_user(buf, ..., ...);
7 ...
8 }
9 /* 写设备*/
10 ssize_t xxx_write(struct file *filp, const char _ _user *buf, size_t count,
11 loff_t *f_pos)
12 {
13 ...
14 copy_from_user(..., buf, ...);
15 ...
16 }
17 /* ioctl 函数 */
18 int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
19 unsigned long arg)
20 {
21 ...22 switch (cmd)
23 {
24 case XXX_CMD1:
25 ...
26 break;
27 case XXX_CMD2:
28 ...
29 break;
30 default:
31 /* 不能支持的命令 */
32 return - ENOTTY;
33 }
34 return 0;
35 }
设备驱动的读函数中,filp 是文件结构体指针,buf 是用户空间内存的地址,该地址在内核空间不能直
接读写,count 是要读的字节数,f_pos 是读的位置相对于文件开头的偏移。
设备驱动的写函数中,filp 是文件结构体指针,buf 是用户空间内存的地址,该地址在内核空间不能直
接读写,count 是要写的字节数,f_pos 是写的位置相对于文件开头的偏移。
由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_from_user()完成用户空间到内核空
间的复制,函数copy_to_user()完成内核空间到用户空间的复制。
copy_from_user()和copy_to_user()的原型如下所示:
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);
上述函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。
如果要复制的内存是简单类型,如 char、int、long 等,则可以使用简单的put_user()和get_user(),如
下所示:
int val; //内核空间整型变量
...
get_user(val, (int *) arg); //用户空间到内核空间,arg 是用户空间的地址
...
put_user(val, (int *) arg); //内核空间到用户空间,arg 是用户空间的地址
读和写函数中的_ _user 是一个宏,表明其后的指针指向用户空间,这个宏定义如下:
#ifdef _ _CHECKER_ _
# define _ _user _ _attribute_ _((noderef, address_space(1)))
#else
# define _ _user
#endif
I/O 控制函数的cmd 参数为事先定义的I/O 控制命令,而arg 为对应于该命令的参数。例如对于串行设
备,如果SET_BAUDRATE 是一个设置波特率的命令,那后面的arg 就应该是波特率值。
在字符设备驱动中,需要定义一个file_operations 的实例,并将具体设备驱动的函数赋值给
file_operations 的成员,如代码清单6.7 所示。
代码清单6.7 字符设备驱动文件操作结构体模板
1 struct file_operations xxx_fops =
2 {
3 .owner = THIS_MODULE,
4 .read = xxx_read,
5 .write = xxx_write,
6 .ioctl = xxx_ioctl,
7 ...
8 };
上述xxx_fops 在代码清单6.5 第11 行的cdev_init(&xxx_dev.cdev, &xxx_fops)的语句中被建立与cdev
的连接。

补充:从linux3.4.48内核中摘抄的代码:

第一段:PIC驱动程序代码(源代码),比资料上给的要详细的多。没有吃过猪肉,也要看看猪是怎么跑的。

#ifdef CONFIG_PCI
#ifdef CONFIG_PM
/**
 * xxxfb_suspend - Optional but recommended function. Suspend the device.
 * @dev: PCI device
 * @msg: the suspend event code.
 *
 *      See Documentation/power/devices.txt for more information
 */
static int xxxfb_suspend(struct pci_dev *dev, pm_message_t msg)
{
 struct fb_info *info = pci_get_drvdata(dev);
 struct xxxfb_par *par = info->par;

 /* suspend here */
 return 0;
}

/**
 * xxxfb_resume - Optional but recommended function. Resume the device.
 * @dev: PCI device
 *
 *      See Documentation/power/devices.txt for more information
 */
static int xxxfb_resume(struct pci_dev *dev)
{
 struct fb_info *info = pci_get_drvdata(dev);
 struct xxxfb_par *par = info->par;

 /* resume here */
 return 0;
}
#else
#define xxxfb_suspend NULL
#define xxxfb_resume NULL
#endif /* CONFIG_PM */

static struct pci_device_id xxxfb_id_table[] = {
 { PCI_VENDOR_ID_XXX, PCI_DEVICE_ID_XXX,
   PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16,
   PCI_CLASS_MASK, 0 },
 { 0, }
};

/* For PCI drivers */
static struct pci_driver xxxfb_driver = {
 .name =  "xxxfb",
 .id_table = xxxfb_id_table,
 .probe = xxxfb_probe,
 .remove = __devexit_p(xxxfb_remove),
 .suspend =      xxxfb_suspend, /* optional but recommended */
 .resume =       xxxfb_resume,  /* optional but recommended */
};

MODULE_DEVICE_TABLE(pci, xxxfb_id_table);

int __init xxxfb_init(void)
{
 /*
  *  For kernel boot options (in 'video=xxxfb:<options>' format)
  */
#ifndef MODULE
 char *option = NULL;

 if (fb_get_options("xxxfb", &option))
  return -ENODEV;
 xxxfb_setup(option);
#endif

 return pci_register_driver(&xxxfb_driver);
}

static void __exit xxxfb_exit(void)
{
 pci_unregister_driver(&xxxfb_driver);
}
#else /* non PCI, platform drivers */
#include <linux/platform_device.h>
/* for platform devices */

#ifdef CONFIG_PM
/**
 * xxxfb_suspend - Optional but recommended function. Suspend the device.
 * @dev: platform device
 * @msg: the suspend event code.
 *
 *      See Documentation/power/devices.txt for more information
 */
static int xxxfb_suspend(struct platform_device *dev, pm_message_t msg)
{
 struct fb_info *info = platform_get_drvdata(dev);
 struct xxxfb_par *par = info->par;

 /* suspend here */
 return 0;
}

/**
 * xxxfb_resume - Optional but recommended function. Resume the device.
 * @dev: platform device
 *
 *      See Documentation/power/devices.txt for more information
 */
static int xxxfb_resume(struct platform_dev *dev)
{
 struct fb_info *info = platform_get_drvdata(dev);
 struct xxxfb_par *par = info->par;

 /* resume here */
 return 0;
}
#else
#define xxxfb_suspend NULL
#define xxxfb_resume NULL
#endif /* CONFIG_PM */

static struct platform_device_driver xxxfb_driver = {
 .probe = xxxfb_probe,
 .remove = xxxfb_remove,
 .suspend = xxxfb_suspend, /* optional but recommended */
 .resume = xxxfb_resume,   /* optional but recommended */
 .driver = {
  .name = "xxxfb",
 },
};

static struct platform_device *xxxfb_device;

#ifndef MODULE
    /*
     *  Setup
     */

/*
 * Only necessary if your driver takes special options,
 * otherwise we fall back on the generic fb_setup().
 */
int __init xxxfb_setup(char *options)
{
    /* Parse user specified options (`video=xxxfb:') */
}
#endif /* MODULE */

static int __init xxxfb_init(void)
{
 int ret;
 /*
  *  For kernel boot options (in 'video=xxxfb:<options>' format)
  */
#ifndef MODULE
 char *option = NULL;

 if (fb_get_options("xxxfb", &option))
  return -ENODEV;
 xxxfb_setup(option);
#endif
 ret = platform_driver_register(&xxxfb_driver);

 if (!ret) {
  xxxfb_device = platform_device_register_simple("xxxfb", 0,
        NULL, 0);

  if (IS_ERR(xxxfb_device)) {
   platform_driver_unregister(&xxxfb_driver);
   ret = PTR_ERR(xxxfb_device);
  }
 }

 return ret;
}

static void __exit xxxfb_exit(void)
{
 platform_device_unregister(xxxfb_device);
 platform_driver_unregister(&xxxfb_driver);
}
#endif /* CONFIG_PCI */

 

 

第二段代码是函数里面的malloc()函数的原形,暂时我还是有些看不懂,

不过如果面试的时候,你可以写出来mallc()函数的话,面试官一定刮目相看,

这个也是源代码:

//下面的三行在别处定义的,我用来说明数据类型的

#ifndef STATIC_RW_DATA
# define STATIC_RW_DATA static
#endif

STATIC_RW_DATA unsigned long malloc_ptr;

STATIC_RW_DATA int malloc_count;

static void *malloc(int size)
{
 void *p; //估计这个是在内核空间直接申请的内存,暂时我还搞不清楚。

 if (size < 0)
  return NULL;
 if (!malloc_ptr)
  malloc_ptr = free_mem_ptr;//在别处定义的unsigned long free_mem_ptr;unsigned long free_mem_end_ptr;在其他的地方都有相关的记录和修改

 malloc_ptr = (malloc_ptr + 3) & ~3;     /* Align *///这里有点深奥,暂时参悟不透。

 p = (void *)malloc_ptr;//从没有使用的内存中分配内存
 malloc_ptr += size;//分配size个字节

 if (free_mem_end_ptr && malloc_ptr >= free_mem_end_ptr)//表示申请的内存没有超过没有使用的内存
  return NULL;

 malloc_count++;//内存申请记录计数,应该是给后期释放的时候有作用吧,可能智能指针就是使用了这个计数来自动释放内存。个人想法
 return p;//返回申请的内存的首地址。
}

小结:

今天晚上在纸上做了笔记,所以看的就比较慢,但是记得的东西比前几天光在电脑上看要深刻的多。

我自己的笔记记录的比较简单,字数比较多,不好往博客上抄录。所以大部分将pdf上的直接复制过来了。

以前别人面试叫我写一个malloc函数。不会写,被人家鄙视了。原理我是懂的,就是没有看见过源码是怎么实现的。

今天算是见到了,理论与实际相结合。

今天还是收获很多的。

1、内核空间和用户空间的数据交换函数;

2、file_operation函数的实现与文件操作息息相关。

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值