/***********************************************************************************
*
* 字符设备驱动基本操作及调用流程
*
* 声明:
* 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. JavaScript基础(三)

    十三.JS中的面向对象 创建对象的几种常用方式 1.使用Object或对象字面量创建对象 2.工厂模式创建对象 3.构造函数模式创建对象 4.原型模式创建对象 1.使用Object或对象字面量创建对象 ...

  2. Space Ant

    Space Ant The most exciting space discovery occurred at the end of the 20th century. In 1999, scient ...

  3. Python学习之路【第一篇】-Python简介和基础入门

    1.Python简介 1.1 Python是什么 相信混迹IT界的很多朋友都知道,Python是近年来最火的一个热点,没有之一.从性质上来讲它和我们熟知的C.java.php等没有什么本质的区别,也是 ...

  4. spring boot整合shiro后,部分注解(Cache缓存、Transaction事务等)失效的问题

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/elonpage/article/details/78965176 前言 整合有缓存.事务的sprin ...

  5. python 首次安装 报错

    最近python很火,想在空余时间学习一波,但是安装完Python后运行发现居然报错了,错误代码是0xc000007b,于是通过往上查找发现是因为首次安装Python缺乏VC++库的原因 错误提示如下 ...

  6. caffe模型参数解释

    作者:wjmishuai 出处: http://blog.csdn.net/wjmishuai/article/details/50890214 原始数据是28*28 1:数据层: layer { n ...

  7. pdf及word文档的读取 pyPDF2,docx

    #!python3 #-*- coding:utf8 -*- #PyPDF2可能会打不开某些pdf文档,也不能提取图片,图表或者其他媒介从PDF文件中.但是它能提取文本从PDF中,转化为字符. imp ...

  8. SQL-30 使用子查询的方式找出属于Action分类的所有电影对应的title,description

    题目描述 film表 字段 说明 film_id 电影id title 电影名称 description 电影描述信息 CREATE TABLE IF NOT EXISTS film ( film_i ...

  9. Centos7部署kubernetes集群CA证书创建和分发(二)

    1.解压软件包 [root@linux-node1 ~]# cd /usr/local/src/ [root@linux-node1 src]# ls k8s-v1.10.1-manual.zip [ ...

  10. IDEA中不同项目配置不同JDK

    安装JDK后,选择其中一个配置环境变量,在IDEA中可以给不同项目选择不同的JDK版本.如下图两个项目: