/***********************************************************************************
*
* 字符设备驱动基本操作及调用流程
*
* 声明:
* 1. 本系列文档是在vim下编辑,请尽量是用vim来阅读,在其它编辑器下可能会
* 不对齐,从而影响阅读.
* 2. 以下所有的shell命令都是在root权限下运行的;
*
* 2015-3-7 阴 深圳 尚观 Sbin 曾剑锋
**********************************************************************************/ \\\\\\\\\\\\\\--*目录*--//////////////
| 一. make编译快捷方式;
| 二. ctags使用;
| 三. menuconfig编译成内核内部模块;
| 四. 编译内核模块的方法;
| 五. 模块操作;
| 六. 多源文件编译模块Makefile格式;
| 七. 导出符号;
| 八. printk打印等级;
| 九. 模块传参;
| 十. 字符设备;
| 十一. 2种字符设备注册;
| 十二. 驱动中常见的3种结构体;
| 十三. 内核空间与用户空间数据拷贝;
| 十四. 驱动被调用函数流程:
\\\\\\\\\\\\\\\\\\\/////////////////// 一. make编译快捷方式:
. export CC=arm-linux-gcc
. make app
arm-linux-gcc app.c -o app 二. ctags使用:
. 生成tags文件 ctags -Rn .
. 把tags文件的路径名添加到vim的配置文件中
cat >> ~/.vimrc << EOF
set tags+=/root/linux-3.5/tags #可以添加多个原文件目录
EOF
. vim查找符号定义: :ts <symbols> 三. menuconfig 编译成内核内部模块:
. cat > test.c << EOF
#include <linux/module.h>
int test_init(void)
{
printk("Hello module.\n");
return ;
}
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
. 在test.c所在目录的Makefile文件添加:
obj-$(CONFIG_TEST) += test.o
. 在test.c所在目录的Kconfig文件添加:
config TEST
bool "----- test module -------"
. 这时候可以通过menuconfig等配置工具配置test模块的编译
. 重新编译内核
. make -j2 zImage
. 查看模块是否编译到内核
. nm vmlinux | grep test_init
c08d0790 t __initcall_test_init6
c030537c T test_init
. nm vmlinux | grep test_exit
c0305370 T test_exit
. 重新把内核烧写到SD卡的kernel分区,dwn或者fastboot都行.
. 系统启动时将调用初始化函数test_init,使用dmesg命令查看是否有正确的输出 四. 编译内核模块的方法
. Makefile中对变量的引用,可以是$(变量名),也可以是${变量名},但是目前看到$(变量名)居多.
. 以下是几个对模块编译的make命令:
. make -C $(内核跟目录路径) M=`pwd` modules
. make -C $(内核根目录路径) M=`pwd` clean
. make -s -C $(内核根目录路径) M=$PWD INSTALL_MOD_PATH=$(nfs文件系统根目录) modules_install
. 实现了上面make命令的shell脚本实例:
cat > mm << EOF
#!/bin/bash
KERNEL=/disk/A9/filesystem/linux-3.5
ROOT_PATH=/disk/A9/filesystem
if [ $# -eq ]
then
make -s -C ${KERNEL} M=$PWD modules
elif [ $# -eq -a "$1" = "clean" ]
then
make -s -C ${KERNEL} M=$PWD modules clean
elif [ $# -eq -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
4. 实现上面make命令的Makefile实例:
ifneq ($(KERNELRELEASE),)
   obj-m := at24c02.o
else 
KDIR := /home/myzr/myandroid/kernel_imx
all:
     make -C $(KDIR) M=$(PWD) modules clean:
      rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order
endif
5. 将mm当常用命令来是用: cp mm /bin/ && chmod /bin/mm
五. 模块操作:
. 动态插入模块到当前运行的系统: insmod test.ko
. 查看当前运行的系统加载的模块信息: lsmod
. 查看模块的信息: modinfo test.ko 或者 modinfo test
. 卸载加载的模块: rmmod test
. 生成模块的依赖关系: depmod
. 加载内核模块,主要用于加载make install的模块: modprobe test
. 卸载内核模块,主要用于卸载make install的模块: modprobe -r test 六. 多源文件编译模块Makefile格式:
. xxx是模块文件名
. obj-m += xxx.o
xxx-objs = main.o foo.o ... 七. 导出符号:
把符号导出到内核全局符号表,主要是为其他的模块提供函数调用,有两种方式:
. EXPORT_SYMBOL(foo); //普通方式
. EXPORT_SYMBOL_GPL(foo); //只有声明为GPL的模块才能调用 八. printk打印等级:
. 数字越小,等级越高:
#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>"
. cat /proc/sys/kernel/printk 数字解析如下:
. ---> 打印等级小于5的内核消息输出到控制台
. ---> 默认的打印等级
. ---> 允许设置的最小等级
. ---> 允许设置的最大等级 九. 模块传参:
. 声明定义可传参变量:
int num = ;
module_param(num, int, );
module_param参数说明:
. num 参数名
. int 参数类型
. 访问权限(下面文件)
. 加载模块时,传参方法: insmod test.ko num=
num =
. cat /sys/module/test/parameters/num 十. 字符设备:
. dev_t devno; ---> 设备号,设备的身份证号码
. 高12位: 主设备号
. 低20位: 次设备号
. 设备号操作辅助宏
. major = MAJOR(devno);
. minor = MINOR(devno);
. devno = MKDEV(major, minor);
. 查看当前系统中注册的所有设备
cat /proc/devices
. 手动创建设备节点
. mknod /dev/test0 c
. ls /dev/test0 -l
crw-r--r-- , Jan : / 十一. 2种字符设备注册:
字符设备底层接口实现linux-3.5/fs/char_dev.c
. static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
//该函数调用了下面的3步注册方式
return __register_chrdev(major, , , name, fops);
}
. 3步详细注册:
. struct cdev cdev; //char device
. 分配设备号,有2种方式:
. 动态分配设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
. 静态指定设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
. 初始化cdev结构
cdev_init();
. 添加cdev到系统中
cdev_add(); 十二. 驱动中常见的3种结构体:
. struct file_operations; //每个驱动对应一个
. struct inode *inode; //每个文件对应一个
. struct file *file; //文件每打开一次,对应一个file结构维护着打开文件的相关信息
. loff_t f_pos; //文件指针
. unsigned int f_flags; //文件访问标志 十三. 内核空间与用户空间之间拷贝数据:
#include <linux/uaccess.h>
. copy_to_user();
. copy_from_user();
成功返回0,失败返回未完成拷贝的字节数 十四. 驱动被调用函数流程:
. 文件IO系统调用 ---> VFS(虚拟文件系统层) ---> 设备驱动
. 系统调用入口定义:arch/arm/kernel/calls.S
. open系统调用对应的内核入口:sys_open,该函数在VFS实现对应源文件fs/open.c;
. sys_open函数定义:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
. 关键函数调用do_sys_open();
. 跟踪该函数do_sys_open(),通过get_unused_fd_flags()返回一个可用的文件描述符,
关键函数调用do_filp_open();
. 跟踪该函数do_filp_open(),
关键函数调用path_openat();
. 跟踪该函数path_openat();
关键函数调用do_last();
. 跟踪该函数do_last();
关键函数调用nameidata_to_filp();
. 跟踪该函数nameidata_to_filp();
. 关键函数调用do_dentry_open();
. 关键步骤:
//把文件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;
} . 那2中的inode里的i_fop是哪里来的
. linux-3.5/fs/inode.c
. 初始化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);
}
. 如果是字符设备,使用def_chr_fops:
const struct file_operations def_chr_fops = {
.open = chrdev_open,
.llseek = noop_llseek,
};
.接下来,跟踪chrdev_open()函数
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = ; 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 ; out_cdev_put:
cdev_put(p);
return ret;
}

Samsung_tiny4412(驱动笔记03)----字符设备驱动基本操作及调用流程的更多相关文章

  1. 【linux驱动笔记】字符设备驱动相关数据结构与算法

    欢迎转载,转载时需保留作者信息,谢谢. 邮箱:tangzhongp@163.com 博客园地址:http://www.cnblogs.com/embedded-tzp Csdn博客地址:http:// ...

  2. LCD驱动分析(一)字符设备驱动框架分析

    参考:S3C2440 LCD驱动(FrameBuffer)实例开发<一>   S3C2440 LCD驱动(FrameBuffer)实例开发<二> LCD驱动也是字符设备驱动,也 ...

  3. 《linux设备驱动开发详解》笔记——6字符设备驱动

    6.1 字符设备驱动结构 先看看字符设备驱动的架构: 6.1.1 cdev cdev结构体是字符设备的核心数据结构,用于描述一个字符设备,cdev定义如下: #include <linux/cd ...

  4. Linux驱动开发2——字符设备驱动

    1.申请设备号 #include <linux/fs.h> int register_chrdev_region(dev_t first, unsigned int count, char ...

  5. Linux 驱动框架---cdev字符设备驱动和misc杂项设备驱动

    字符设备 Linux中设备常见分类是字符设备,块设备.网络设备,其中字符设备也是Linux驱动中最常用的设备类型.因此开发Linux设备驱动肯定是要先学习一下字符设备的抽象的.在内核中使用struct ...

  6. 【Linux 驱动】简单字符设备驱动架构(LED驱动)

    本文基于icool210开发板,内核版本:linux2.6.35: 驱动代码: (1)头文件:led.h #ifndef __LED_H__ #define __LED_H__ #define LED ...

  7. Linux 设备驱动--- 阻塞型字符设备驱动 --- O_NONBLOCK --- 非阻塞标志【转】

    转自:http://blog.csdn.net/yikai2009/article/details/8653697 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 阻塞 阻 ...

  8. Linux驱动开发之字符设备驱动模型之file_operations

    90%的驱动模型都是按照下图开发的 下面来说下设备描述结构是什么东西 打开Linux-2.6.32.2的Source Insight 工程,搜索cdev 比如一个应用程序需要调用read和write这 ...

  9. 字符设备驱动、平台设备驱动、设备驱动模型、sysfs的比较和关联

    转载自:http://www.kancloud.cn/yueqian_scut/emlinux/106829 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sy ...

随机推荐

  1. Ajax请求中的async:false/true的作用【转载】

    [Ajax请求中的async:false/true的作用] 作者:https://www.cnblogs.com/mmzuo-798/p/7098979.html 前言: 昨天在做倒计时修改的时候,发 ...

  2. 前端页面兼容ie8解决方法

    一.通用兼容文件的引用: 1.HTML5标签兼容方案:html5shiv.js GitHub地址:https://github.com/aFarkas/html5shiv/ IE8不支持HTML5的新 ...

  3. 二、持久层框架(Hibernate)

    一.Hibernate对象的状态 实体类对象在Hibernate中有3中状态:瞬时,持久,脱管. 瞬时:没有和Hibernate发生任何关系,在数据库中也没有对应的记录,一旦JVM结束,对象就消失了 ...

  4. telnet强制中断登录

    在telnet登录的时候,有时我们只是想测试某个账号密码是否正确 但是telnet不像ssh一样密码试错之后可以使用Ctrl+c强制中断,使如果要输错三次五次才给退出中断交互那是十分浪费时间和心情的 ...

  5. 整合elk(1)(十二)

    elk 简介 Elasticsearch是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等. Logstash是 ...

  6. 针对unicode对象---检测字符串是否只由数字组成

  7. studio配置本地gradle-x.x.x-all.zip

    在引入别的项目时,一般会突然一直卡在了building...,下载网络gradle. 我们从网络下载gradle.zip到本地,通过将.\项目\gradle\wrapper下的gradle-wrapp ...

  8. JAVA常识1

    DBA:                     https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%A1%E7%90%86% ...

  9. sigmoid belief network boltszmann machine

    because of explaining away, the hidden weights in sigmoid belief network is no longer independent

  10. GitHub下的文件放到Linux系统下

    1.在GitHub账号下clone URL 项目. 2.到Linux服务器下执行以下操作: (1)  mkdir test (2)  cd test/ (3)  git clone  复制的项目URL