/*********************************************************************************** * * Samsung_tiny4412(笔记)-->字符设备驱动基本操作及调用流程 * * 声明: * 以下所有的shell命令都是在root权限下运行的; * * 2015-3-7 阴 深圳 尚观 Sbin 曾剑锋 **********************************************************************************/ \\\\\\\\\\\\\\--*目录*--////////////// | 一. make编译快捷方式; | 二. ctags使用; | 三. menuconfig编译成内核内部模块; | 四. 编译内核模块的方法; | 五. 模块操作; | 六. 多源文件编译模块Makefile格式; | 七. 导出符号; | 八. printk打印等级; | 九. 模块传参; | 十. 字符设备; | 十一. 2种字符设备注册; | 十二. 驱动中常见的3种结构体; | 十三. 内核空间与用户空间数据拷贝; | 十四. 驱动被调用函数流程: \\\\\\\\\\\\\\\\\\\/////////////////// 一. make编译快捷方式: 1. export CC=arm-linux-gcc 2. make app arm-linux-gcc app.c -o app 二. ctags使用: 1. 生成tags文件 ctags -Rn . 2. 把tags文件的路径名添加到vim的配置文件中 cat >> ~/.vimrc << EOF set tags+=/root/linux-3.5/tags EOF 3. vim查找符号定义: :ts <symbols> 三. menuconfig 编译成内核内部模块: 1. cat > test.c << EOF #include <linux/module.h> int test_init(void) { printk("Hello module.\n"); return 0; } void test_exit(void) { printk("Bye module.\n"); } //指定模块的初始化函数与退出函数 module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("lizhichao"); MODULE_DESCRIPTION("simpile module."); MODULE_VERSION("1.0"); EOF 2. 在test.c所在目录的Makefile文件添加: obj-$(CONFIG_TEST) += test.o 3. 在test.c所在目录的Kconfig文件添加: config TEST bool "----- test module -------" 4. 这时候可以通过menuconfig等配置工具配置test模块的编译 5. 重新编译内核 6. make -j2 zImage 7. 查看模块是否编译到内核 1. nm vmlinux | grep test_init c08d0790 t __initcall_test_init6 c030537c T test_init 2. nm vmlinux | grep test_exit c0305370 T test_exit 8. 重新把内核烧写到SD卡的kernel分区,dwn或者fastboot都行. 9. 系统启动时将调用初始化函数test_init,使用dmesg命令查看是否有正确的输出 四. 编译内核模块的方法 1. Makefile中对变量的引用,可以是$(变量名),也可以是${变量名},但是目前看到$(变量名)居多. 2. 以下是几个对模块编译的make命令: 1. make -C $(内核跟目录路径) M=`pwd` modules 2. make -C $(内核根目录路径) M=`pwd` clean 3. make -s -C $(内核根目录路径) M=$PWD INSTALL_MOD_PATH=$(nfs文件系统根目录) modules_install 3. 实现了上面make命令的shell脚本实例: cat > mm << EOF #!/bin/bash KERNEL=/disk/A9/filesystem/linux-3.5 ROOT_PATH=/disk/A9/filesystem if [ $# -eq 0 ] then make -s -C ${KERNEL} M=$PWD modules elif [ $# -eq 1 -a "$1" = "clean" ] then make -s -C ${KERNEL} M=$PWD modules clean elif [ $# -eq 1 -a "$1" = "install" ] then make -s -C ${KERNEL} M=$PWD INSTALL_MOD_PATH=${ROOT_PATH} modules_install else echo "usage:" echo " mm" echo " mm clean" echo " mm install" fi EOF 五. 模块操作: 1. 动态插入模块到当前运行的系统: insmod test.ko 2. 查看当前运行的系统加载的模块信息: lsmod 3. 查看模块的信息: modinfo test.ko 或者 modinfo test 4. 卸载加载的模块: rmmod test 5. 生成模块的依赖关系: depmod 6. 加载内核模块,主要用于加载make install的模块: modprobe test 7. 卸载内核模块,主要用于卸载make install的模块: modprobe -r test 六. 多源文件编译模块Makefile格式: 1. xxx是模块文件名 2. obj-m += xxx.o xxx-objs = main.o foo.o ... 七. 导出符号: 把符号导出到内核全局符号表,主要是为其他的模块提供函数调用,有两种方式: 1. EXPORT_SYMBOL(foo); //普通方式 2. EXPORT_SYMBOL_GPL(foo); //只有声明为GPL的模块才能调用 八. printk打印等级: 1. 数字越小,等级越高: #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant condition */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ /* Use the default kernel loglevel */ #define KERN_DEFAULT "<d>" 2. cat /proc/sys/kernel/printk 5 4 1 7 数字解析如下: 1. 5 ---> 打印等级小于5的内核消息输出到控制台 2. 4 ---> 默认的打印等级 3. 1 ---> 允许设置的最小等级 4. 7 ---> 允许设置的最大等级 九. 模块传参: 1. 声明定义可传参变量: int num = 500; module_param(num, int, 0644); module_param参数说明: 1. num 参数名 2. int 参数类型 3. 0644 访问权限(下面文件) 2. 加载模块时,传参方法: insmod test.ko num=1234 num = 1234 3. cat /sys/module/test/parameters/num 1234 十. 字符设备: 1. dev_t devno; ---> 设备号,设备的身份证号码 1. 高12位: 主设备号 2. 低20位: 次设备号 2. 设备号操作辅助宏 1. major = MAJOR(devno); 2. minor = MINOR(devno); 3. devno = MKDEV(major, minor); 3. 查看当前系统中注册的所有设备 cat /proc/devices 4. 手动创建设备节点 1. mknod /dev/test0 c 250 0 2. ls /dev/test0 -l crw-r--r-- 1 0 0 250, 0 Jan 1 15:31 /0 十一. 2种字符设备注册: 字符设备底层接口实现linux-3.5/fs/char_dev.c 1. static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { //该函数调用了下面的3步注册方式 return __register_chrdev(major, 0, 256, name, fops); } 2. 3步详细注册: 1. struct cdev cdev; //char device 2. 分配设备号,有2种方式: 1. 动态分配设备号 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, 2. 静态指定设备号 int register_chrdev_region(dev_t from, unsigned count, const char *name) 3. 初始化cdev结构 cdev_init(); 4. 添加cdev到系统中 cdev_add(); 十二. 驱动中常见的3种结构体: 1. struct file_operations; //每个驱动对应一个 2. struct inode *inode; //每个文件对应一个 3. struct file *file; //文件每打开一次,对应一个file结构维护着打开文件的相关信息 1. loff_t f_pos; //文件指针 3. unsigned int f_flags; //文件访问标志 十三. 内核空间与用户空间之间拷贝数据: #include <linux/uaccess.h> 1. copy_to_user(); 2. copy_from_user(); 成功返回0,失败返回未完成拷贝的字节数 十四. 驱动被调用函数流程: 1. 文件IO系统调用 ---> VFS(虚拟文件系统层) ---> 设备驱动 2. 系统调用入口定义:arch/arm/kernel/calls.S 1. open系统调用对应的内核入口:sys_open,该函数在VFS实现对应源文件fs/open.c; 2. sys_open函数定义: SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) 3. 关键函数调用do_sys_open(); 3. 跟踪该函数do_sys_open(),通过get_unused_fd_flags()返回一个可用的文件描述符, 关键函数调用do_filp_open(); 4. 跟踪该函数do_filp_open(), 关键函数调用path_openat(); 5. 跟踪该函数path_openat(); 关键函数调用do_last(); 6. 跟踪该函数do_last(); 关键函数调用nameidata_to_filp(); 7. 跟踪该函数nameidata_to_filp(); 1. 关键函数调用do_dentry_open(); 2. 关键步骤: //把文件inode的file_operations 保存在file结构里 f->f_op = fops_get(inode->i_fop); if (!open && f->f_op) open = f->f_op->open; if (open) { //调用file_operations的open成员函数 error = open(inode, f); if (error) goto cleanup_all; } 3. 那2中的inode里的i_fop是哪里来的 1. linux-3.5/fs/inode.c 2. 初始化inode结构的i_fop,调用init_special_inode函数: void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev) { inode->i_mode = mode; if (S_ISCHR(mode)) { //如果是字符设备,使用def_chr_fops inode->i_fop = &def_chr_fops; inode->i_rdev = rdev; } else if (S_ISBLK(mode)) { inode->i_fop = &def_blk_fops; inode->i_rdev = rdev; } else if (S_ISFIFO(mode)) inode->i_fop = &def_fifo_fops; else if (S_ISSOCK(mode)) inode->i_fop = &bad_sock_fops; else printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for" " inode %s:%lu\n", mode, inode->i_sb->s_id, inode->i_ino); } 3. 如果是字符设备,使用def_chr_fops: const struct file_operations def_chr_fops = { .open = chrdev_open, .llseek = noop_llseek, }; 4.接下来,跟踪chrdev_open()函数 static int chrdev_open(struct inode *inode, struct file *filp) { struct cdev *p; struct cdev *new = NULL; int ret = 0; spin_lock(&cdev_lock); p = inode->i_cdev; if (!p) { struct kobject *kobj; int idx; spin_unlock(&cdev_lock); //找到之前注册的字符设备时添加的cdev结构的kobj kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); if (!kobj) return -ENXIO; //通过container_of获取cdev结构的地址 new = container_of(kobj, struct cdev, kobj); spin_lock(&cdev_lock); /* Check i_cdev again in case somebody beat us to it while we dropped the lock. */ p = inode->i_cdev; if (!p) { inode->i_cdev = p = new; list_add(&inode->i_devices, &p->list); new = NULL; } else if (!cdev_get(p)) ret = -ENXIO; } else if (!cdev_get(p)) ret = -ENXIO; spin_unlock(&cdev_lock); cdev_put(new); if (ret) return ret; ret = -ENXIO; //把字符设备驱动的file_operations保存在file结构里 filp->f_op = fops_get(p->ops); if (!filp->f_op) goto out_cdev_put; if (filp->f_op->open) { //调用file_operations结构的open成员 ret = filp->f_op->open(inode, filp); if (ret) goto out_cdev_put; } return 0; out_cdev_put: cdev_put(p); return ret; }
时间: 2024-10-24 06:07:06