最简单的内核模块
#include
#include
#include
static int __init init_testko(void)
{
printk("test ko init\n");
return 0;
}
static void __exit exit_testko(void)
{
printk("test ko exit\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("KEVIN");
module_init(init_testko);
module_exit(exit_testko);
Makefile写法
obj-m = test.o #模块源代码
KER_DIR = /root/linux-3.0 #内核源码路径
CUR_DIR = $(shell pwd)
all:
make -C $(KER_DIR) M=$(CUR_DIR) modules
clean:
rm -rf *.o *.ko *.mod.o *.mod.c *.symvers *.order
cp:
cp *.ko /root/rootfs/
在内核中设备号用一个32位数dev_t(linux/types.h)表示,高十二位表示主设备号,低二十位表示次设备号。
#include
MAJOR(dev_t dev);
MINOR(dev_t dev);
以上来两个宏分别从dev_t中提取主设备号和次设备号。下边的宏用来合成一个设备号。
MKDEV(int major, int minor);
静态分配
int register_chrdev_region(dev_t first, unsigned int count, char * name);
动态分配
int alloc_chrdev_region(dev_t *dev, unsigned int dirstminor, unsigned int count, char * name);
这两个函数在分配成功的时候返回0,分配失败的时候返回一个错误码。
释放占用的设备号
void unregister_chrdev_region(dev_t first, unsigned int count);
参数:
first 要分配的设备编号范围的启始值
count 所请求的连续设备编号的个数
name 是和该编号范围关联的设备名,将出现在/proc/devices和sysfs中
dev 是用来保存分配的设备号的内存地址
关于错误码,内核定义了一组宏来表示,这些宏保存在./include/asm-generic/errno-base.h中
可以在写驱动代码的时候使用,例如
return -EPERM;
表示操作没有权限,使用的时候需要加上‘-’符号。
关于设备号的分配
内核中有块设备表和字符设备表,根据设备的类型和主设备号可通过这两个表之一找到对应的驱动程序函数跳转结构,而次设备号则一般只用做同类型设备中具体设备项的编号。
linux2.6之前的额版本采用8位的数据类型存储主设备号,因此将块设备和字符设备的种类都限制到了256种。内核源代码中关于设备号分配最重要的一个函数是__register_chrdev_region,此函数主要维护了一个指针数组chrdevs,以散列表的形式管理设备。而register_chrdev_region和alloc_chrdev_region都间接的调用了这个函数,其中alloc_chrdev_region更是直接以第一个参数为0的方式调用,具体的讲解见http://blog.csdn.net/virlhs/article/details/51711112
此处还有一个疑问:在以动态分配形式分配设备号时__register_chrdev_region从255向前搜索chrdevs数组,找到一个指向NULL的指针时就返回此指针的索引(数组下标)作为动态分配的主设备号,但是如果数组chrdevs中的指针全不为NULL,是不是会分配失败,也就是说动态分配只能分配0-255的主设备号?(先Mark一下,以后探究)
#include
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);
每一个字符设备都对应一个cdev结构,注册设备的过程就是分配和初始化cdev结构的过程,因此常使用如下代码完成
动态创建
struct cdev * my_cdev = cdev_alloc();
my_cdev->ops = &my_cdev;
my_cdev->owner = THIS_MODULE;
result = cdev_add(my_cdev, devno, count);
在设备注销时调用
cdev_del(my_cdev);
静态创建
struct cdev mcdev;
struct file_operations fops;
cdev_init(&mcdev, &fops);
mcdev.owner = THIS_MODULE;
result = cdev_add(&mycdev, devno, count);
在设备注销的时候调用
cdev_del(&mcdev);
int register_chrdev(unsigned int major, const char * name, struct file_operations * fops);
major : 主设备号,为0时表示自动分配
name : 驱动程序的名字,将在/proc/devices中显示
fops : 默认的file_operations结构
int unregister_chrdev(unsigned int major, const char * name);
这种注册设备的方法是最经常看到的,据说是以前老版本的内核推荐的方法,个人感觉虽然这种方法使用起来方便,但是
却屏蔽了很多细节,对于学系内核来说并不是很直观。
使用此种方法注册的驱动程序,主次设备号都不能大于255
下面看一下几个函数的调用关系
register_chrdev
|---- __register_chrdev
|---- __register_chrdev_region
|---- cdev_alloc
|---- cdev_add
可以看出register_chardev函数封装了,分配设备号的__register_chrdev_region函数和注册设备的cdev_alloc函数和cdev_add函数,就是将上边的过程封装了起来,
自动注册设备节点需要mdev的支持,(PC平台叫做udev)mdev会以守护进程的形式运行,在后台监听sysfs的uevent事件,自动在/dev,目录下创建相应的设备节点。
为了满足这样的需求,根文件系统首先需要相应的支持。
1、必须存在sysfs文件系统 mount -t sysfs sysfs /sys
2、/dev目录需要挂载为tmpfs mount -t tmpfs mdev /dev
3、在设备开机时启动mdev, 在/etc/init.d/rcS中添加echo /bin/mdev > /proc/sys/kernel/hotplug && mdev -s
其次代码中也要添加创建设备节点的相应代码。
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#define class_create(owner, name)
/**
* class_destroy - destroys a struct class structure
* @cls: pointer to the struct class that is to be destroyed
*
* Note, the pointer to be destroyed must have been created with a call
* to class_create().
*/
void class_destroy(struct class *cls)
{
if ((cls == NULL) || (IS_ERR(cls)))
return;
class_unregister(cls);
}
class_create 是一个宏,参数有两个,owner指所属模块,一般为THIS_MODULE,name为设备类名,
此宏调用了 __class_create(owner, name, &__key); 函数,调用关系如下
__class_create
| ---- __class_register
| ---- kset_init
| ---- kobject_set_name
| ---- kset_register
| ---- kobject_add_internal
| ---- kobject_uevent v
| ---- add_class_attrs
| ---- class_create_file
/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
/**
* device_destroy - removes a device that was created with device_create()
* @cla: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered
*
* This call unregisters and cleans up a device that was created with a
* call to device_create().
*/
void device_destroy(struct class *class, dev_t devt)
device_createde 函数的调用关系如下:
device_createde
| ---- device_create_vargs
| ---- device_register
| ---- device_initialize
| ---- device_add
| ---- kobject_add
| ---- device_create_file
| ---- device_add_class_symlinks
| ---- kobject_uevent
这四个函数的的头文件都是 #include
下面是一个例子
#include
#include
#include
#include // for cdev_add
#include // for class_create
#include //for IS_ERR
#include //for alloc_chrdev_region
static dev_t devno;
static struct cdev * dev;
static struct class * class;
static struct device * device;
static struct file_operations ops = {
.owner = THIS_MODULE,
};
static int __init init_testko(void)
{
printk("test ko init\n");
if (alloc_chrdev_region(&devno, 0, 1, "Hello")) {
printk("device number register failed!\n");
return -1;
}
printk("device number register success, major : %u\n", MAJOR(devno));
dev = cdev_alloc();
dev->owner = THIS_MODULE;
dev->ops = &ops;
if (cdev_add(dev, devno, 1)) {
printk("cdev_add failed!\n");
unregister_chrdev_region(devno, 1);
return -1;
}
class = class_create(THIS_MODULE, "HELLO");
if (IS_ERR(class)) {
cdev_del(dev);
unregister_chrdev_region(devno, 1);
return PTR_ERR(class);
}
device = device_create(class, NULL,devno, NULL, "hello");
if (IS_ERR(device)) {
class_destroy(class);
cdev_del(dev);
unregister_chrdev_region(devno, 1);
return PTR_ERR(device);
}
return 0;
}
static void __exit exit_testko(void)
{
device_destroy(class, devno);
class_destroy(class);
cdev_del(dev);
unregister_chrdev_region(devno, 1);
printk("test ko exit\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("KEVIN");
module_init(init_testko);
module_exit(exit_testko);
这个实例代码可以完成使用Insmod命令插入模块的时候自动在/dev目录下创建hello设备文件。