2015-09-10 13:32:08 Chihiro_S 阅读数 379
  • Android底层技术:HAL驱动开发

    本课程提供开发者学习Android底层的HAL(硬件抽象层)的开发方法和技术。HAL所在的位置是介于Android系统服务与Linux内核之间,HAL Driver是以library形式出现,给HAL Stub调用,供Android System架构者调用。而HAL Stub则是google设计出来的,保护硬件厂商的硬件驱动。

    17789 人正在学习 去看看 高煥堂

本文转自迅为4412精英版群:

 

本期实验比较简单,就是写一个简单的应用程序调用前面写的驱动。

 

硬件工具

1iTOP4412 开发板

2盘或者 TF 

3PC 

4)串口

9.1.1.2软件工具

1)虚拟机 Vmware

2Ubuntu12.04.2

3)超级终端(串口助手)

4)实验配套源码文件夹“invoke_hello

 

视频资源

本节配套视频为“视频 09-编写简单应用调用驱动”

 

学习目标

本章需要学习以下内容:

学会调用设备节点

 

实验操作

本期实验很简单,在前面 Linux 应用中就已经学习过设备节点的调用。

需要用到函数 extern void printf(const char *format,...);定义在标准 语言头文件

stdio.h 中。

 

下面几个头文件在应用中一般一起调用。

头文件 #include <sys/types.h>包含基本系统数据类型。系统的基本数据类型在 32 

译环境中保持为 32 位值,并会在 64 编译环境中增长为 64 位值。

头文件<sys/stat.h>包含系统调用文件的函数。可以调用普通文件、目录、管道、socket

字符、块的属性。

<fcntl.h>定义了 open 函数

<unistd.h>定义了 close 函数

<sys/ioctl.h>定义了 ioctl 函数

 

另外提醒一下,这些头文件是和编译器在一起。

这里使用,如下图所示,进入目录“/usr/local/arm/arm-2009q3”。

 

 

 

使用查找命令“find ./ -name stat.h,如下图所示,使用的头文件是目录

/arm-none-linux-gnueabi/libc/usr/include/sys/stat.h”中的<sys/stat.h>.


 

其它几个头文件可以采用类似的方法查找,这里给大家提醒这一点,因为有时候拿到源码

之后,可能编译器版本和源码不完全对应,这个时候就有可能需要修改和处理一下头文件。不过这种问题一般都可以通过网络查找错误提示的方法一个一个解决。

如下图所示,是一个简单的调用程序。

 

 

 

新建“invoke_hello”文件夹,将上图的中的文件拷贝进入,进入新建的“invoke_hello

目录,使用编译命令

arm-none-linux-gnueabi-gcc -o invoke_hello invoke_hello.c -static

编译,如下图所示。

 


 

 

将 “invoke_hello” 拷贝到 盘, 启动开发板, 加载前一期的 “devicenode_linux_module

驱动,如下图所示,使用 invoke_hello 调用设备节点“/dev/hello_ctl123”。

先使用命令“mount /dev/sda1 /mnt/udisk/”加载 盘;

使用命令“insmod /mnt/udisk/devicenode_linux_module.ko”加载驱动;

使用命令“./mnt/udisk/invoke_hello,运行 invoke_hello

 

 

 

如上图所示,运行“invoke_hello”之后,会打印以下内容”

 

hello open

cmd is 1,arg is 6

hello release

如下图所示,设备节点 opencloseioctl 分别对应打印信息

printk(KERN_EMERG "hello open\n");

printk(KERN_EMERG "hello release\n");

printk("cmd is %d,arg is %d\n",cmd,arg);ioctl 会打印第二个和第三个参数。

 

 

 

通过前面的分析,可以看到上层应用对设备节点 opencloseioctl 分别对应驱动层的

openreleaseunlocked_ioctl

2015-08-18 13:47:28 Chihiro_S 阅读数 1054
  • Android底层技术:HAL驱动开发

    本课程提供开发者学习Android底层的HAL(硬件抽象层)的开发方法和技术。HAL所在的位置是介于Android系统服务与Linux内核之间,HAL Driver是以library形式出现,给HAL Stub调用,供Android System架构者调用。而HAL Stub则是google设计出来的,保护硬件厂商的硬件驱动。

    17789 人正在学习 去看看 高煥堂

Linux驱动教程http://pan.baidu.com/s/1c0hljUS


编写简单应用调用驱动——头文件

• 打印头文件

– include <stdio.h>调用打印函数printf

• 应用中调用文件需要的头文件

– #include <sys/types.h>基本系统数据类型。系统的基本数据类型在32 编译

 

环境中保持为32 位值,并会在64 编译环境中增长为64 位值。

– #include <sys/stat.h>系统调用函数头文件。可以调用普通文件,目录,管

道,socket,字符,块的属性

– #include <fcntl.h>定义了open函数

– #include <unistd.h>定义了close函数

– #include <sys/ioctl.h>定义了ioctl函数

• 调用的头文件是和编译器放在一起的

– 这里使用arm2009q3编译器,编译器使用arm-none-linux-gnueabi-gcc

• 在编译器目录下使用查找命令找到该头文件

– 例如#find ./ -name types.h

• 调用的函数

– open函数是返回文件描述符

– ioctl函数是应用向驱动传值

– close函数是关闭打开的文件

• 编写应用程序的代码,编译

– arm-none-linux-gnueabi-gcc -o invoke_hello invoke_hello.c -static

• 开发板中加载devicenode_linux_module驱动,运行应用

2019-05-27 09:49:42 yinsui1839 阅读数 81
  • Android底层技术:HAL驱动开发

    本课程提供开发者学习Android底层的HAL(硬件抽象层)的开发方法和技术。HAL所在的位置是介于Android系统服务与Linux内核之间,HAL Driver是以library形式出现,给HAL Stub调用,供Android System架构者调用。而HAL Stub则是google设计出来的,保护硬件厂商的硬件驱动。

    17789 人正在学习 去看看 高煥堂

安卓驱动开发和linux驱动开发是一摸一样的,只不过安卓驱动要被上层应用调用到与linux的方式不同

Android 应用调用驱动:

上层apk---->jni层--->驱动层   (这只是一个访问的路径示意图,中间还需要为SElinux和init.rc赋予给apk访问驱动权限问题)

一,驱动层:
1.在驱动路径下新建test目录和修改Makefile

cjx@ubuntu:~/work/linux$ mkdir drivers/test 
cjx@ubuntu:~/work/linux$ vim drivers/Makefile

drivers/Makefile文件末尾添加

obj-y                           += test/

2.新建test.c文件和Makefile

cjx@ubuntu:~/work/linux$ cd drivers/test
cjx@ubuntu:~/work/linux/drivers/test$ vim test.c;vim Makefile

test.c内容如下

#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/input/sparse-keymap.h>
#include <linux/gpio_keys.h>

#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>       


typedef struct _test_t {
	timming_t timing;
	unsigned long long pininfo;
	RTK_GPIO_ID gid;
	struct cdev cdev;
	struct class *class;
}test_t;
static dev_t devno_base;


static
int test_open(struct inode *inode, struct file *file)
{
	printk(KERN_ALERT  "cjx open================================\n");

	return 0;
}

static
int test_release(struct inode *inode, struct file *file)
{
	return 0;
}


static
long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	printk(KERN_ALERT  "cjx============================\n", cmd, arg);
	return 0;
}

static struct file_operations test_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = test_ioctl,
	.open = test_open,
	.release = test_release,
};

static char *test_devnode(struct device *dev, mode_t *mode)
{
	if(mode)
		*mode = 0666;
	return NULL;
}


static int __init test_module_init(void)
{
	unsigned int result = 0;
	int major_node = 0;
	int minor_node = 0;
	
	if (alloc_chrdev_region(&devno_base, 0, 1, "test") != 0)
			return -EFAULT;
		
	major_node = MAJOR(devno_base);
	test_info.class = class_create(THIS_MODULE, "test-dev"); 
	if (IS_ERR(test_info.class)) {
			result = PTR_ERR(test_info.class);
			goto err_create_class_failed;
	}
	test_info.class->devnode = test_devnode;  
	
	cdev_init(&test_info.cdev, &test_fops);
	test_info.cdev.owner = THIS_MODULE;
	test_info.cdev.ops = &test_fops;
	cdev_add (&test_info.cdev, MKDEV(major_node, minor_node), 1);
	device_create(test_info.class, NULL, MKDEV(major_node, minor_node), NULL, "test");

	return 0;

err_create_class_failed:
	unregister_chrdev_region(devno_base, 1);
	return result;
}

static void __exit test_module_exit(void)
{
	device_destroy(test_info.class, MKDEV(0, 0));
	cdev_del(&test_info.cdev);
    unregister_chrdev_region(devno_base, 1);
	class_destroy(test_info.class);
}

MODULE_LICENSE("GPL");
module_init(test_module_init);
module_exit(test_module_exit);

Makefile:

obj-y += test.o

到这里驱动层已经添加完毕,编译烧录后会产生/dev/test文件

二:应用层和jni层:

在要调用的驱动的apk的代码下新建test.java

mkdir src/android/hikeen/
vim src/android/hikeen/test.java

 

/***test.java:***/
package android.hikeen;

public class test {
        private static test mtest = null;
        private static boolean isOpen = false;

        static {
                System.loadLibrary("test");
        }
        public native void inittest();
        public native void test(int direction, int repeat, int time);
        public native void move(int direction, int repeat, int time);
        public native void closetest();
        public native int istesting();

        public static test getInstance(){
                if (mtest == null) {
                        mtest = new test();
                }
                return mtest;
        };

        public void open(){
                if (!isOpen) {
                        inittest();
                }
                isOpen = true;
        };

        public void close(){
                if (isOpen) {
                        closetest();
                }
                isOpen = false;
        };
}
cd src;
javah android.hikeen.test   //在当前文件夹下就会产生android_hikeen_test.h文件,这个就是jni c++的头文件

一般写jni的位置在源码/external下
mkdir 源码/external/test/
将android_hikeen_test.h 移到 源码/external/test/
然后在源码/external/test/路径下新建Android.mk  android_hikeen_test.cpp

/***android_hikeen_test.cpp***/
#include <jni.h>
#include <android/bitmap.h>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>

#include <math.h>                                                                                                    

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <string.h>

#include <errno.h>
#include <linux/input.h>
#include <stdint.h>


#include <pthread.h>

#define SCROLL_UP		0
#define SCROLL_DOWN		1
#define SCROLL_LEFT		2
#define SCROLL_RIGHT	3
#include<android/log.h>

#define MOVE_UP			11
#define MOVE_DOWN		22

#include "android_hikeen_test.h"

#ifdef __cplusplus
extern "C" {
#endif
int fd;

#define TAG "myDemo-jni" // 这个是自定义的LOG的标识 
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型 
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型 
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型 
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型 
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型 

JNIEXPORT void JNICALL Java_android_hikeen_test_inittest(JNIEnv *, jobject)
  {
	  fd = open("/dev/test", O_RDWR);
	  if(fd < 0) {
		LOGD("open test file error %d",fd);
      }
	
  }

JNIEXPORT void JNICALL Java_android_hikeen_test_test(JNIEnv *, jobject , jint direction, jint repeat, jint time)
{
	int i=direction+repeat+time,ret;
	LOGD(" test test i= %d",i);
	ret=ioctl(fd,1);
	
	if(ret<0){
		LOGD("ioctl test file error %d",ret);
	}
	return ;
}

JNIEXPORT void JNICALL Java_android_hikeen_test_move(JNIEnv *, jobject , jint direction, jint repeat, jint time)
{
	int i=direction+repeat+time,ret=0;
	LOGD(" test test i= %d",i);
	ret=ioctl(fd,1);
	if(ret<0){
		LOGD(" move test file error %d",ret);
	}
	return ;
	
}

JNIEXPORT void JNICALL Java_android_hikeen_test_closetest(JNIEnv *, jobject)
  {
	  close(fd);
	  LOGD(" close test file error");
  }
JNIEXPORT jint JNICALL Java_android_hikeen_test_istesting(JNIEnv *, jobject)
  {
	  return 0;
  }

#ifdef __cplusplus
}
#endif
/***Android.mk***/
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE  := libtest


LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES

LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code -Wunused-parameter

LOCAL_SRC_FILES := android_hikeen_test.cpp

LOCAL_MODULE_TAGS := eng debug

	
LOCAL_LDLIBS := -ljnigraphics	
LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

编译烧录完成后在板子上的system/lib/下就有libtest.so这个文件,这个就是test 的jni库

到现在还未完成,因为还需要赋权限和SELinux的权限才能正常调用

2019-04-30 00:28:24 a568713197 阅读数 497
  • Android底层技术:HAL驱动开发

    本课程提供开发者学习Android底层的HAL(硬件抽象层)的开发方法和技术。HAL所在的位置是介于Android系统服务与Linux内核之间,HAL Driver是以library形式出现,给HAL Stub调用,供Android System架构者调用。而HAL Stub则是google设计出来的,保护硬件厂商的硬件驱动。

    17789 人正在学习 去看看 高煥堂

生成设备节点

Linux到2.6版本时改动巨大,针对以前版本的资料直接跳过
学习要“以始为终”工作用不到的就不去浪费时间

关于杂项设备

杂项设备(设备号10)
对一部分字符设备的封装,还有一部分不好归类驱动也归到了杂项设备
为什么引入杂项设备

  • 节省主设备号
    如果所有的驱动都是用字符设备,那么所有的设备号很快就用完了,总共255个设备号
  • 驱动写起来相对简单
    如果直接使用封装好的杂项设备,那么就可以减少一步注册主设备号的过程

初始化部分源文件在“driver/char/misc.c”强制编译,Linux官方带的,为了一些简单的驱动更容易实现,给字符设备做了一个简单的封装
杂项设备注册头文件“linux/miscdevice.h”

	struct miscdevice  {
		int minor;//设备号,一般自动分配
		const char *name;//生成设备节点的名称(随意)
		const struct file_operations *fops;//指向一个设备节点文件
		struct list_head list;
		struct device *parent;
		struct device *this_device;
		const char *nodename;
		mode_t mode;
	};

两个重要的函数

extern int misc_register(struct miscdevice * misc);//杂项设备节点注册
extern int misc_deregister(struct miscdevice *misc);//卸载

杂项设备内核文件结构体

注册设备节点本质也就是新建了一个特殊文件,包含文件名、打开、关闭、操作等函数
包含文件结构体的头文件是“linux/fs.h”
文件的结构体file_operations如下 非常重要!!
在这里插入图片描述
参数很多,根据需求选择
必选的参数是:

  • .owner 一般是THIS_MODULE
  • .open 打开文件函数
  • .release 关闭文件函数
    这里在必选之外使用参数
  • .unlocked_ioctl对GPIO操作,应用向底层驱动传值

如何生成设备节点

代码

#include <linux/init.h>
#include <linux/module.h>
/*driver register*/
#include <linux/platform_device.h>

/*注册杂项设备头文件*/
#include <linux/miscdevice.h>
/*注册设备节点的文件结构体*/
#include <linux/fs.h>

#define DRIVER_NAME "hello_ctl"
#define DEVICE_NAME "hello_ctl_dev"//设备名
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("GYY");

/*打开文件函数*/
static int hello_open(struct inode * pinode , struct file * pfile )
{
	printk(KERN_EMERG "Hello OPEN !!\n");
	return 0;
}
/*关闭文件函数*/
static int hello_release(struct inode * pinode, struct file * pfile)
{
	printk(KERN_EMERG "Hello RELEASE !!\n");
	return 0;
}
/*IO控制函数*/
static long hello_ioctl(struct file * pfile, unsigned int cmd, unsigned long arg)
{
	printk("cmd is %d,arg is %d\n",cmd,arg);
	return 0;
}
/*杂项设备内核文件结构体,要注册函数*/
static struct file_operations hello_ops = {
	.owner = THIS_MODULE,//所有者,THIS_MODULE
	.open = hello_open,
	.release = hello_release,
	.unlocked_ioctl = hello_ioctl,
	
};

/*杂项设备节点结构体*/
static struct miscdevice hello_dev = {
	.minor = MISC_DYNAMIC_MINOR,//自动分配设备号
	.name = DEVICE_NAME,//设备名
	.fops = &hello_ops,//该成员为杂项设备内核文件结构体
};


static int hello_probe (struct platform_device *pdv){
	
	printk(KERN_EMERG "\tinitialized\n");
	/*生成设备节点*/
	misc_register(&hello_dev);
	
	return 0;
}

static int hello_remove (struct platform_device *pdv){
	
	printk(KERN_EMERG "\tremove\n");
	misc_deregister(&hello_dev);
	return 0;
}

static void hello_shutdown (struct platform_device *pdv){
	
	
}

static int hello_suspend (struct platform_device *pdv,pm_message_t state){
	
	return 0;
}

static int hello_resume (struct platform_device *pdv){
	
	return 0;
}


struct platform_driver hello_driver = {
	.probe = hello_probe,
	.remove = hello_remove,
	.shutdown = hello_shutdown,
	.suspend = hello_suspend,
	.resume = hello_resume,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	}
};


static int hello_init(void)
{
	int DriverState;
	printk(KERN_EMERG "HELLO WORLD enter!\n");
	DriverState=platform_driver_register(&hello_driver);
	
	printk(KERN_EMERG "\t%d\n",DriverState);
	return 0;
}

static void hello_exit(void)
{
	printk(KERN_EMERG "HELLO WORLD exit!\n");
	platform_driver_unregister(&hello_driver);
}

module_init(hello_init);
module_exit(hello_exit);

代码分析

接下来我们对代码进行简要的分析,下面的部分和我们前面注册驱动的代码是基本相同的,我们在hello_probe (也就是驱动的初始化函数)中调用了misc_register()来注册杂项设备节点,杂项设备节点就像是一个挂载在设备上的设备一样,它的本质也是一个设备,所以说如果注册成功我们应该可以在/dev/中查看到我们注册的设备hello_ctl_dev。我们在hello_remove中调用了misc_deregister()来卸载杂项设备节点,说明我们rmmod这个模块后/dev/中的hello_ctl_dev就不存在了。

结果演示

安装模块
在这里插入图片描述
查看/dev/
在这里插入图片描述
在这里插入图片描述
存在hello_ctl_dev,设备节点生成成功
卸载模块
在这里插入图片描述
查看/dev/
在这里插入图片描述
我们的设备节点不存在了

编写简单应用调用驱动

基本原理

在前面我们生成了设备节点,而且也给设备节点注册了内核文件结构体,同时在内核文件结构体中我们也注册了open、close、ioctl的函数,我们做这些的目的是什么呢,当然是将这些函数供给上层的应用使用,这样才能完成我们驱动开发的使命。
当然,Linux一切皆文件的准则大大的方便了我们的操作,我们只要把/dev/中的设备节点作为一个文件进行操作那么就可以调用我们的驱动, Linux系统调用中的文件操作被映射为我们所写的驱动函数(这个过程还不是特别的了解),我们调用系统调用的函数,实质上执行了我们驱动中的函数。

写代码前应知道的

需要的头文件

打印头文件:#include <stdio.h>
应用中调用文件需要的头文件

  • #include <sys/types.h>基本系统数据类型。系统的基本数据类型在32位编译环境中保持32位值,并会在64位编译环境中增长为64位值
  • #include <sys/stat.h>系统调用函数头文件。可以调用普通文件,目录,管道,socket,字符,块的属性
  • #include <fcntl.h>定义了open函数
  • #include <unistd.h>定义了close函数
  • #include <sys/ioctl.h>定义了ioctl函数

调用的函数

open函数是返回文件描述符
ioctl是应用向驱动传值
close关闭打开的文件

编译代码

使用交叉编译器 arm-none-linux-gnueabi-gcc

实验

代码

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main()
{
	int fd;//文件描述符
	char *hello_node = "/dev/hello_ctl_dev";//文件地址
	
	/*O_RDWR可读可写,O_NDELAY非阻塞方式打开*/
	if((fd = open(hello_node,O_RDWR|O_NDELAY)) < 0)
	{
		printf("APP open %s failed\n",hello_node);
	}
	else
	{
		printf("APP open %s success\n",hello_node);
		ioctl(fd,1,6);//调用ioctl
	}
	close(fd);//关闭文件
}

代码分析

我们调用open函数来打开文件并返回了文件描述符,当我们执行open函数时,系统会执行我们在驱动代码中所写的hello_open这个函数,应当会打印“Hello OPEN !!”,如果文件描述符不小于0(打开成功)那么会打印我们在应用程序中所写的“APP open /dev/hello_ctl_dev success”,接下来我们调用了ioctl函数,系统会去调用我们在驱动代码中所写的hello_ioctl函数,这个函数将我们的参数打印了出来,所以会打印“cmd is 1,arg is 6”,接下来我们调用close关闭文件,系统会去调用我们在驱动代码中所写hello_release函数,这个函数会打印“Hello RELEASE !!”,这就是我们整个应用程序的分析。
注意:由于printk打印和printf打印的优先级问题,应用程序中的printf打印会在最后被打印,并不是程序逻辑的错误

结果演示

注意:要先生成设备节点在执行应用程序
执行应用程序
在这里插入图片描述
说明我们的分析是正确的,系统调用的文件操作函数调用了我们在驱动中所写的函数,我们做到了对上层的接口。

对设备节点与设备注册、驱动注册的区分

生成节点的代码可以放到任何地方,和驱动注册和设备注册关系不是那么严密,甚至没有驱动注册,也是可以生成设备节点的
我们在前面的设备节点生成时在驱动注册的时候在probe函数中生成了设备节点,但这并不是必须的,我们完全可以在init函数中进行设备节点的生成。

代码演示

#include <linux/init.h>
#include <linux/module.h>
/*driver register*/
#include <linux/platform_device.h>

/*注册杂项设备头文件*/
#include <linux/miscdevice.h>
/*注册设备节点的文件结构体*/
#include <linux/fs.h>

#define DEVICE_NAME "hello_ctl_dev"//设备名
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("GYY");

static int hello_open(struct inode * pinode , struct file * pfile )
{
	printk(KERN_EMERG "Hello OPEN !!\n");
	return 0;
}

static int hello_release(struct inode * pinode, struct file * pfile)
{
	printk(KERN_EMERG "Hello RELEASE !!\n");
	return 0;
}

static long hello_ioctl(struct file * pfile, unsigned int cmd, unsigned long arg)
{
	printk("cmd is %d,arg is %d\n",cmd,arg);
	return 0;
}

static struct file_operations hello_ops = {
	.owner = THIS_MODULE,
	.open = hello_open,
	.release = hello_release,
	.unlocked_ioctl = hello_ioctl,
	
};

static struct miscdevice hello_dev = {
	.minor = MISC_DYNAMIC_MINOR,//自动分配设备号
	.name = DEVICE_NAME,//设备名
	.fops = &hello_ops,
};

static int hello_init(void)
{
	printk(KERN_EMERG "HELLO WORLD enter!\n");
	misc_register(&hello_dev);	
	return 0;
}

static void hello_exit(void)
{
	printk(KERN_EMERG "HELLO WORLD exit!\n");
	misc_deregister(&hello_dev);
}

module_init(hello_init);
module_exit(hello_exit);

在这个代码中我们删掉了所有关于驱动注册的部分,直接在hello_init函数中进行设备节点的生成,但是我们的内核文件结构体是没有改变的,说明我们要实现的功能是相同的

测试

安装模块
在这里插入图片描述
执行应用程序
在这里插入图片描述
应用程序执行的结果和我们上面的结果是相同的

重点区分

设备节点是“对上”的,为了让应用程序可以调用
一定注意生成设备节点和设备注册没有关系,而且设备节点名称不需要和设备名称相同
一般情况下,是将设备节点注册放到probe中,但是放到init函数中的驱动也是有的

2017-05-18 20:02:52 u013377887 阅读数 853
  • Android底层技术:HAL驱动开发

    本课程提供开发者学习Android底层的HAL(硬件抽象层)的开发方法和技术。HAL所在的位置是介于Android系统服务与Linux内核之间,HAL Driver是以library形式出现,给HAL Stub调用,供Android System架构者调用。而HAL Stub则是google设计出来的,保护硬件厂商的硬件驱动。

    17789 人正在学习 去看看 高煥堂

前言

在前边三节的基础上,粗略的分析一下,上层应用调用到驱动程序的过程,分为下面几个方面:
1.字符设备驱动本身
2.mknod的作用
3.open的调用过程

正文

字符设备驱动本身

start_kernel(kernel-3.10\init\main.c)//启动内核
        vfs_caches_init(totalram_pages);
        ---------
             vfs_caches_init (kernel-3.10\fs\dcache.c)
                    inode_init(); //节点初始化
                    chrdev_init();//字符设备 (kernel-3.10\fs\Char_dev.c)
                            //得到一个kobj_map 类型的cdev_map,用户字符设备的设备号
                            cdev_map = kobj_map_init(base_probe, &chrdevs_lock);                  
                            //涉及到cdev_map的一些操作函数:cdev_add cdev_del chrdev_open。 

mknod

mknod 命令简历一个目录项和一个特殊文件的对应索引点。
mknod的系统调用过程,看参考文献。
在这个过程中,会调用 init_special_inode 这个函数,

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev) (kernel-3.10\fs\Inode.c) 
{
    inode->i_mode = mode;
    if (S_ISCHR(mode)) {
        inode->i_fop = &def_chr_fops; // 这个给inode的i_fop 赋值为 def_chr_fops,
        inode->i_rdev = rdev;          //inode->i_rdev 赋值为设备号
        }
def_chr_fops的内容
/*
 * Dummy default file-operations: the only thing this does
 * is contain the open that then fills in the correct operations
 * depending on the special file...
 */
const struct file_operations def_chr_fops = {
    .open = chrdev_open,
    .llseek = noop_llseek,
};

可以看出def_chr_fops是一个 file_operations类型的变量,其有个成员open,被赋值为 chrdev_open

/*
 * Called every time a character special file is opened
 */
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);
        kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); // 当调用这个的opend的时候,会根据设备号在cdev_map中查找设备
        if (!kobj)
            return -ENXIO;
        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;
    filp->f_op = fops_get(p->ops);
    if (!filp->f_op)
        goto out_cdev_put;

    if (filp->f_op->open) {
        ret = filp->f_op->open(inode, filp); //调用字符设备的open函数
        if (ret)
            goto out_cdev_put;
    }

    return 0;

 out_cdev_put:
    cdev_put(p);
    return ret;
}

open的调用过程

应用程序调用open,会 调用系统的sys_open函数
最终会调用到

struct file *dentry_open(const struct path *path, int flags,const struct cred *cred)(kernel-3.10\fs\Open.c)
    f->f_flags = flags;
    f->f_path = *path;
    rror = do_dentry_open(f, NULL, cred);
            struct inode *inode;
            inode = f->f_inode = f->f_path.dentry->d_inode; 
            f->f_op = fops_get(inode->i_fop);//获得设备节点对应的fops结构体。会是def_chr_fops 这个结构体
                if (!open && f->f_op)
                open = f->f_op->open; // 会调用def_chr_fops结构体的open,即chrdev_open。调用过程,看    mknod  部分的介绍

总结

使用mknod 创建节点,在创建过程中的def_chr_fops的open,会起一个 中转的作用。 在调用open打开设备的时候,
会调用到设备本身的open函数,实现打开设备的功能。
目前的代码,我们是手动的调用mknod创建节点。除了手动创建设备节点,linux还提供了 class_create(),device_create自动创建设备文件结点

参考文献

字符设备文件的打开
Linux系统调用(syscall)原理
Linux内核源代码情景分析-系统调用mknod
class_create(),device_create自动创建设备文件结点

没有更多推荐了,返回首页