1.linux内核API:register_chrdev
函数register_chrdev()调用函数__register_chrdev()实现其功能,函数__register_chrdev()首先调用函数__register_chrdev_region()创建一个字符设备区,此设备区的主设备号相同,由函数register_chrdev()的第一个参数决定,次设备号的变化范围是0~256,设备区的名字为函数register_chrdev()的第二个参数,此函数将更改
/proc/devices
文件的内容;然后动态申请一个新的字符设备cdev结构体变量,对其部分字段进行初始化,初始化完成之后将其加入Linux内核系统中,即向Linux内核系统添加一个新的字符设备。函数register_chrdev()调用函数cdev_alloc()动态申请一个字符设备,调用函数cdev_add()将其加入Linux内核系统中。
输入参数:
- 函数register_chrdev()有三个输入参数,第一个输入参数是unsigned int型的变量,代表动态申请字符设备的主设备号,对于此设备号函数自动赋值为0。
- 第二个输入参数是char型的指针,代表申请设备的设备名。
- 第三个输入参数是struct file_operations结构体类型的指针,代表申请设备的操作函数,通过此结构体包含的函数完成对设备的访问及控制操作。
返回参数:
- 函数register_chrdev()返回int型的结果,表示设备添加是否成功。如果成功返回0,如果失败返回-ENOMEM, ENOMEM的定义值为12。
2.linux内核API:class_create
宏class_create()用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。此函数的执行效果就是在目录
/sys/class
下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的。宏class_create()在实现时,调用了函数__class_create(),作用和函数__class_create()基本相同。
输入参数:
宏class_create()有两个输入参数,分别解释如下:
- 参数
owner
是一个struct module结构体类型的指针,指向函数__class_create()即将创建的struct class类型对象的拥有者,一般赋值为THIS_MODULE。- 参数
name
是char类型的指针,代表即将创建的struct class变量的名字,用于给struct class的name字段赋值。返回参数:
- 宏class_create()的返回值与函数__class_create()的返回值相同,都代表新创建的逻辑类,在函数__class_create()的分析文档的返回参数说明部分已对此结构体进行了详细的说明。
3.linux内核API:device_create
函数device_create()用于动态地创建逻辑设备,并对新的逻辑设备类进行相应的初始化,将其与此函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到Linux内核系统的设备驱动程序模型中。函数能够自动地在
/sys/devices/virtual
目录下创建新的逻辑设备目录,在/dev
目录下创建与逻辑类对应的设备文件。
输入参数:
函数
device_create()
的第一个输入参数代表与即将创建的逻辑设备相关的逻辑类。第二个输入参数代表即将创建的逻辑设备的父设备的指针,子设备与父设备的关系是:当父设备不可用时,子设备不可用,子设备依赖父设备,父设备不依赖子设备。
第三个输入参数是逻辑设备的设备号。
第四个输入参数是void类型的指针,代表回调函数的输入参数。
第五个输入参数是逻辑设备的设备名,即在目录
/sys/devices/virtual
创建的逻辑设备目录的目录名。返回参数:
- 函数
device_create()
的返回值是struct device结构体类型的指针,指向新创建的逻辑设备。
5.linux内核API:class_destroy
函数class_destroy()用于删除设备的逻辑类,即从Linux内核系统中删除设备的逻辑类。此函数执行的效果是删除函数__class_create()或宏class_create()在目录
/sys/class
下创建的逻辑类对应的文件夹。
输入参数:
- 函数
class_destroy()
的输入参数是struct class结构体类型的变量,代表设备的逻辑类。返回参数:
- 函数
class_destroy()
的返回值是void类型的变量,即不返回任何值。
6.linux内核API:unregister_chrdev
函数unregister_chrdev()通过调用函数__unregister_chrdev()实现其功能,函数__unregister_chrdev()首先调用函数__unregister_chrdev_region()删除一个字符设备区,并更改文件
/proc/devices
的内容;然后将一个字符设备从Linux内核系统中删除,如果此字符设备是通过函数cdev_alloc()动态申请的,函数会释放其占用的内存空间。最后调用函数cdev_del()删除字符设备。
输入参数:
- 函数
unregister_chrdev()
有两个输入参数,第一个输入参数代表即将被删除的字符设备区及字符设备的主设备号,函数将根据此参数查找内核中的字符设备。- 第二个输入参数代表设备名,但在函数的实现源码中没有用到,没有什么意义。
返回参数:
- 函数
unregister_chrdev()
的返回void类型的结果,即不返回任何类型的值。
7.hello_drv驱动例程
//hello_drv.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#incldue <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
static int major = 0;//主设备号
static char kernel_buf[1024];
static struct class *hello_class;
#define MIN(a,b) (a < b ? a:b)
static ssize_t hello_drv_read(struct file *file,char __user *buf,size_t size,loff_t *offset)
{
int err;
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
err = copy_to_user(buf,kernel_buf,MIN(1024,size));
return MIN(1024,SIZE);
}
static ssize_t hello_drv_write(struct file *file,const char __user *buf,size_t size,loff_t *offset)
{
int err;
prink("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
err = copy_from_user(kernel_buf,buf,MIN(1024,size));
return MIN(1024,SIZE);
}
static int hello_drv_open(struct inode *node,struct file *file)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}
static int hello_drv_close(struct inode *node, struct file *file)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}
static struct file_operations hello_drv ={
.owner = THIS_MODULE, //内核使用这个字段以避免在模块的操作正在被使用时卸载该模块
.open = hello_drv_open,
.read = hello_drv_read,
.write = hello_drv_write,
.release = hello_drv_close,
};
static int __init hello_init(void)
{
int err;
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
major = regidter_chrdev(0,"hello",&hello_drv);
hello_class = class_create(THIS_MODULE,"hello_class");
err = PTR_ERR(hello_class);
if(IS_ERR(hello_class))
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
unregister_chrdev(major,"hello");
return -1;
}
device_create(hello_class,NULL,MKDEV(major,0),NULL,"hello");
return 0;
}
static void __exit hello_exit(void)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
device_destroy(hello_class,MKDEV(major,0));
class_destroy(hello_class);
unregister_chrdev(major,"hello");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
8. 应用例程
//hello_drv_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
* ./hello_drv_test -w abc
* ./hello_drv_test -r
*/
int main(int argc, char **argv)
{
int fd;
char buf[1024];
int len;
/* 1. 判断参数 */
if (argc < 2)
{
printf("Usage: %s -w <string>\n", argv[0]);
printf(" %s -r\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open("/dev/hello", O_RDWR);
if (fd == -1)
{
printf("can not open file /dev/hello\n");
return -1;
}
/* 3. 写文件或读文件 */
if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
{
len = strlen(argv[2]) + 1;
len = len < 1024 ? len : 1024;
write(fd, argv[2], len);
}
else
{
len = read(fd, buf, 1024);
buf[1023] = '\0';
printf("APP read : %s\n", buf);
}
close(fd);
return 0;
}
9. Makeflie
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f hello_drv_test
obj-m += hello_drv.o
make -C $(KERN_DIR) M=`pwd` modules
该命令是make modules命令的扩展,-C选项的作用是指将当前的工作目录转移到制定的 目录,即(KERN_DIR)目录,程序到(shell pwd)当前目录查找模块源码,将其编译,生成.ko文件。
KERN_DIR表示内核源码目录,这种方式适用于嵌入式开发的交叉编译,KERN_DIR目录中包含了内核驱动模块所需要的各种头文件及依赖。