读《Linux 设备驱动 Edition 3》第二天

来源:互联网 发布:python append和extend 编辑:程序博客网 时间:2024/06/05 02:29

下面是模块驱动程序的hello world及一个标准的Makefile模板。

#include <linux/init.h>

#include <linux/module.h>

static int hello_init(void)
{
printk(KERN_ALERT"Hello,world\n");
return 0;
}

static void hello_exit(void)
{
prink(KERN_ALERT"Goodbye,cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
//-------------------------------------------------------------------------------------------------//

Makefile模板:


#IF KERNELRELEASE is defined,we've been invoked from the
#kernel builid system and can use its language.
ifneq ($(KERNELRELEASE),)

obj-m := hello.o
#Otherwise we were called directly from the command
#line: invoke the kernel build system.
else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

end if
//---------------------------------------------------------------------------------------------------------------------------------------//
内核在加载驱动的模块时可以通过指定可变参数的值,来满足驱动加载时的需求。这些参数的值可由insmod或者modprobe在加载时指定;后者也可以从它的配置文件(/etc/modprobe.conf)读取参数的值。
insmod hellop howmany=10 whom="MOM"
既hellop会说"hello MOM"10次,这里的hello来源于你原本的驱动模块hello.ko。

static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);

模块参数支持许多类型,这里提两个特殊点的,invbool(与bool颠倒),charp(一个字符指针值,内存为用户提供的字串分配,指针因此设置)数组参数(module_param_array(name,type,num,perm);)perm为通常的权限值。

//----------------------------------------------------------------------------------------------------------------------------------------//

字符设备通过文件系统的名字来存取,那些名字称为文件系统的特殊文件或者设备文件,或者文件系统的简单节点,惯例上它们位于/dev目录下,通过ls -l /dev可以获取一系列的设备文件,第一列的"c"标识表明这是个字符设备文件,"b"标识表明这是个块设备文件,接着会有两串数字,分别为主次编号,主编号标识设备相连的驱动,次编号被内核用来决定引用哪个设备。在编写驱动设备文件时可通过以下方式定义主次编号:
#include <linux/kdev_t.h>

MAJOR(dev_t dev);
MINOR(dev_t dev);

可以通过MKDEV(int major,int minor);将其转换成一个dev_t。

设备编号的静态动态分配及释放。
#include <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);

允许驱动分配和释放设备编号的范围的函数. register_chrdev_region 应当用在事先知道需要的主编号时; 对于动态分配, 使用 alloc_chrdev_region 代替.
first是你要分配的起始设备编号,count是你请求的连续设备编号的总数,name是应当连接到这个编号范围的设备的名字,可通过cat /proc/devices查看。

返回值:true return 0;false return -1;


void unregister_chrdev_region(dev_t first,unsigned int count);


个人推荐直接使用动态分配,缺点在于无法提前创建设备节点,但是却防止了主次编号的冲突和麻烦。更好的话则是缺省使用动态分配,同时留给自己在加载时指定主编号的选项权,代码如下:
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;
}

//------------------------------------------------------------------------------------------------------------------------------------//

struct file_operations;
struct file;
struct inode;

大部分设备驱动使用的 3 个重要数据结构. file_operations 结构持有一个字符驱
动的方法; struct file 代表一个打开的文件, struct inode 代表磁盘上的一个文
件.

file_operation 结构是一个字符驱动如何建立这个连接. 这个结构, 定义在 <linux/fs.h>, 是一个函数指针的集合. 每个打开文件(内部用一个 file 结构来代表, 稍后我们会查看)与它自身的函数集合相关连( 通过包含一个称为 f_op 的成员, 它指向一个 file_operations 结构). 这些操作大部分负责实现系统调用, 因此, 命名为 open,read, 等等. 我们可以认为文件是一个"对象"并且其上的函数操作称为它的"方法", 使用面向对象编程的术语来表示一个对象声明的用来操作对象的动作. 这是我们在 Linux 内核中看到的第一个面向对象编程的现象,下面是对file_operation的初始化。

struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};

int (*open)(struct inode *inode, struct file *filp);
int scull_release(struct inode *inode, struct file *filp);
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);

ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);

struct file, 定义于 <linux/fs.h>, 是设备驱动中第二个最重要的数据结构. 注意 file与用户空间程序的 FILE 指针没有任何关系. 一个 FILE 定义在 C 库中, 从不出现在内核代码中. 一个 struct file, 另一方面, 是一个内核结构, 从不出现在用户程序中.文件结构代表一个打开的文件. (它不特定给设备驱动; 系统中每个打开的文件有一个关联的 struct file 在内核空间). 它由内核在 open 时创建, 并传递给在文件上操作的任何函数, 直到最后的关闭. 在文件的所有实例都关闭后, 内核释放这个数据结构.在内核源码中, struct file 的指针常常称为 file 或者 filp("file pointer"). 我们将一直称这个指针为 filp 以避免和结构自身混淆. 因此, file 指的是结构, 而 filp 是结构指针.

inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的. 可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个inode 结构.inode 结构包含大量关于文件的信息. 作为一个通用的规则, 这个结构只有 2 个成员对于编写驱动代码有用:dev_t i_rdev;
对于代表设备文件的节点, 这个成员包含实际的设备编号.struct cdev *i_cdev;struct cdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.i_rdev 类型在 2.5 开发系列中改变了, 破坏了大量的驱动. 作为一个鼓励更可移植编程的方法, 内核开发者已经增加了 2 个宏, 可用来从一个 inode 中获取主次编号:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
为了不要被下一次改动抓住, 应当使用这些宏代替直接操作 i_rdev.

//---------------------------------------------------------------------------------------------------------------------------------------------//

注册、初始化、添加和注销一个字符设备的函数,其中cdev 结构管理的函数, 它代表内核中的字符设备.
#include <linux/cdev.h>
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
void cdev_del(struct cdev *dev);
在内核版本为2.6版本的时候,注册和注销字符设备的函数分别如下,心疼自己只用过这个:

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

int unregister_chrdev(unsigned int major, const char *name);

//------------------------------------------------------------------------------------------------------------------------------------------//

#include <linux/kernel.h>
container_of(pointer, type, field);
一个传统宏定义, 可用来获取一个结构指针, 从它里面包含的某个其他结构的指针.
#include <asm/uaccess.h>
这个包含文件声明内核代码使用的函数来移动数据到和从用户空间.
unsigned long copy_from_user (void *to, const void *from, unsigned long count);
unsigned long copy_to_user (void *to, const void *from, unsigned long count);
在用户空间和内核空间拷贝数据.

//------------------------------------------------------------------------------------------------------------------------------------------//

内核中有2 个核心函数来管理 Linux 内核中的内存. 这些函数 定义在<linux/slab.h>, 是:
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
对 kmalloc 的调用试图分配 size 字节的内存; 返回值是指向那个内存的指针或者如果分配失败为NULL. flags 参数用来描述内存应当如何分配。提及一下,动态获取分配内存及释放。

这篇博文很好的描述了scull字符设备,推荐浏览一下加深理解。http://blog.csdn.net/wooin/article/details/1762818

原创粉丝点击