字符设备驱动1

常用的模块操作命令
(1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表
(2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko
(3)modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息。,用法是modinfo xxx.ko
(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(注意卸载模块时只需要输入模块名即可,不能加.ko后缀)
在ubuntu中使用dmesg命令就可以看到相关的打印信息
在ubuntu中运行的代码
Makefile
/************************************/

这里写代码片
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build 
KERN_DIR=/root/rootfs   //内核源码树目录
obj-m   += module_test.o

all:
    make -C $(KERN_DIR) M=`pwd` modules 

cp:
    cp *.ko /root/porting_x210/rootfs/rootfs/driver_test

.PHONY: clean   
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个

/*****************************************/
KERN_DIR为内核源码树所在路径,如果在开发板中运行,必须是当前开发板运行的linux系统的源码路径,不然可能导致装载失败。
make -C $(KERN_DIR) M=`pwd` modules 
make -C $(KERN_DIR) 进入内核源码树目录,
M=`pwd` modules 记录当前路径,编译完成后返回该路径 
moduletest.c
/***************************************************/
 #include <linux/module.h>      // module_init  module_exit
 #include <linux/init.h>            // __init   __exit
 #include <linux/fs.h>  
    #define MYMAJOR     200
        #define MYNAME testchar
static int test_chrdev_open(struct inode *inode, struct file *file)
{
    // 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
    // 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
    printk(KERN_INFO "test_chrdev_open\n"); 
    return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");  
    return 0;
}
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
    .owner      = THIS_MODULE,              // 惯例,直接写即可

    .open       = test_chrdev_open,         // 将来应用open打开这个设备时实际调用的
    .release    = test_chrdev_release,      // 就是这个.open对应的函数
};

// 模块安装函数
static int __init chrdev_init(void)
{   
    int ret = -1;

    printk(KERN_INFO "chrdev_init helloworld init\n");

    // 在module_init宏调用的函数中去注册字符设备驱动
    ret =register_chrdev(MYMAJOR,testchar,&test_fops);
    if (ret)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "register_chrdev success...\n");

    return 0;
}

// 模块下载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");

    // 在module_exit宏调用的函数中去注销字符设备驱动
    unregister_chrdev(MYMAJOR, MYNAME);

}
module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");              // 描述模块的许可证
MODULE_AUTHOR("aston");             // 描述模块的作者
MODULE_DESCRIPTION("module test");  // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");          // 描述模块的别名信息

module_init宏和insmod xxx.ko是关联在一起的,insmod的时候系统会帮我们调用module_init(chrdev_init);中的函数chrdev_init,同时会把该模块注册到lsmod显示的列表中。
同理module_exit一样。
insmod时模块的vermagic必须和内核的相同,否则不能安装,报错信息为:
insmod: ERROR: could not insert module module_test.ko: Invalid module format
__init,本质上是个宏定义,在内核源代码中就有#define __init xxxx。这个__init的作用就是将被他修饰的函数放入.init.text段中去(本来默认情况下函数是被放入.text段中)。
整个内核中的所有的这类函数都会被链接器链接放入.init.text段中,所以所有的内核模块的__init修饰的函数其实是被统一放在一起的。内核启动时统一会加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存.
驱动源代码中包含的头文件和原来应用编程程序中包含的头文件不是一回事。应用编程中包含的头文件是应用层的头文件,是应用程序的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和操作系统无关的)。驱动源码属于内核源码的一部分,驱动源码中的头文件其实就是内核源代码目录下的include目录下的头文件。
struct file_operations {
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 , const char __user , size_t, loff_t );
ssize_t (aio_read) (struct kiocb , const struct iovec *, unsigned long, loff_t);
ssize_t (aio_write) (struct kiocb , const struct iovec *, unsigned long, loff_t);
int (readdir) (struct file , void *, filldir_t);
unsigned int (poll) (struct file , struct poll_table_struct *);
int (ioctl) (struct inode , struct file *, unsigned int, unsigned long);
long (unlocked_ioctl) (struct file , unsigned int, unsigned long);
long (compat_ioctl) (struct file , unsigned int, unsigned long);
int (mmap) (struct file , struct vm_area_struct *);
int (open) (struct inode , struct file *);
int (flush) (struct file , fl_owner_t id);
int (release) (struct inode , struct file *);
int (fsync) (struct file , int datasync);
int (aio_fsync) (struct kiocb , int datasync);
int (fasync) (int, struct file , int);
int (lock) (struct file , int, struct file_lock *);
ssize_t (sendpage) (struct file , struct page , int, size_t, loff_t , int);
unsigned long (get_unmapped_area)(struct file , unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (flock) (struct file , int, struct file_lock *);
ssize_t (splice_write)(struct pipe_inode_info , struct file , loff_t , size_t, unsigned int);
ssize_t (splice_read)(struct file , loff_t , struct pipe_inode_info , size_t, unsigned int);
int (setlease)(struct file , long, struct file_lock **);
};
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可

.open       = test_chrdev_open,         // 将来应用open打开这个设备时实际调用的
.release    = test_chrdev_release,      // 就是这个.open对应的函数

};

(1)元素主要是函数指针,用来挂接实体函数地址
(2)每个设备驱动都需要一个该结构体类型的变量
(3)设备驱动向内核注册时提供该结构体类型的变量
驱动要通过register chardev()函数去内核注册这个设备,注册后内核有相关信息,应用就可以找到这个驱动。

static inline int register_chrdev(unsigned int major, const char *name,
                  const struct file_operations *fops)
{
    return __register_chrdev(major, 0, 256, name, fops);
}

major 设置为0表示让系统自动分配主设备号,返回值为分配的主设备号,name是设备名称,fops是操作结构体

猜你喜欢

转载自blog.csdn.net/jiushimanya/article/details/82221620