2015-09-10 13:32:08 Chihiro_S 阅读数 386
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5164 人正在学习 去看看 朱有鹏

本文转自迅为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

2020-02-26 15:06:30 gao19874 阅读数 18
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5164 人正在学习 去看看 朱有鹏

1、应用程序如何调用驱动程序

Linux系统通过U-BOOT启动内核,进入内核后,内核调用应用程序执行相应的操作命令,比如打开一个文件(文件操作)、点亮LED、获取去按键值(硬件操作)等,那么对于一个应用程序开发人员来说,他在进行这些操作的时候不可能去查看每一款芯片的操作手册,对于应用程序开发人员来讲,他们通常是通过标准的系统接口(open、read、write)去调用驱动程序来实现上述操作。在这里插入图片描述
上图是应用程序操作硬件的层级结构图,应用程序调用标准C库里面的函数(open、read、write),这些函数执行后会产生一条swi val(swi某个值)异常指令,进而进入内核,内核中的系统调用接口(System call interface)根据发生异常的原因调用不同的处理函数(sys_open、sys_read、sys_write),然后虚拟文件系统(VFS)根据打开的不同文件的不同属性(设备类型、主设备号)最终找到不同的驱动程序去执行。
详细的说就是VFS根据打开文件属性中的设备类型主设备号,比如说字符设备,设备号111,VFS就会去内核的字符设备数组chr_dev (struct file_operations * chrdev[255];) 中按照主设备号去寻找对应驱动程序注册的结构体,该结构体中即是标准C库函数所要执行底层驱动程序。

2、驱动程序框架的编写

(1)编写first_drv_open first_drv_write 函数

static int first_drv_open(struct inode *inode,struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}
static ssize_t first_drv_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
	printk("first_drv_write\n");
	return 0;
}

(2)将函数告知内核(注册进内核)

first_drv_open、first_drv_write 函数最终会被内核调用,因此应该告诉内核,我编写了对应驱动程序的open、write函数,那么如何告知内核呢?

①首先定义一个file_operations结构体,并进行填充
static struct file_operations first_drv_fops={
	.owner = THIS_MODULE ,
	.open  = first_drv_open,
	.write = first_drv_write,
};
②使用函数register_chrdev将结构体注册进内核
register_chrdev(111,"first_drv",&first_drv_fops);
//int register_chrdev(unsigned int major,const char *name,const  struct file_operations *fops);
③编写驱动入口函数调用register_chrdev函数
static int first_drv_init(void)
{
	register_chrdev(111,"first_drv",&first_drv_fops);
	return 0;
}
④使用module_init()宏对入口函数进行修饰

编写好入口函数后,内核如何知道该函数即为某个驱动程序的入口函数呢?答案是使用宏定义 module_init() 对入口函数进行修饰,该宏定义内部有一个结构体,其成员包含一个函数指针,指向对应驱动程序的入口函数,当加载驱动时(insmod),内核就会找到这样的结构体,利用里面的函数指针成员找到驱动程序的入口函数,并执行入口函数代码,入口函数内部为register_chrdev函数,执行后即在内核中对应的chrdev (struct file_operations * chrdev[255];) 数组中将 struct file_operations first_drv_fops 结构体注册进去,major即索引,即结构体指针存储的位置。

module_init(first_drv_init);
⑤完整驱动框架代码如下
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static int first_drv_open(struct inode *inode,struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}

static ssize_t  first_drv_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
	printk("first_drv_write\n");
	return 0;
}
//a 定义结构
static struct file_operations first_drv_fops={
	.owner = THIS_MODULE ,
	.open  = first_drv_open,
	.write = first_drv_write,
};
//c 函数被谁调用		 谁==驱动入口库函数
static int first_drv_init(void)
{
	//b 把结构告诉内核
	register_chrdev(111,"first_drv",&first_drv_fops);
	return 0;
}
static void first_drv_exit(void)
{
	unregister_chrdev(major,"first_drv");//卸载驱动
}

//d 修饰函数  
module_init(first_drv_init);
module_exit(first_drv_exit);
2015-08-18 13:47:28 Chihiro_S 阅读数 1067
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5164 人正在学习 去看看 朱有鹏

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 阅读数 135
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5164 人正在学习 去看看 朱有鹏

安卓驱动开发和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 阅读数 531
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5164 人正在学习 去看看 朱有鹏

生成设备节点

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函数中的驱动也是有的

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