当前位置: 首页 > news >正文

Linux(一)最简单的LED驱动程序(应用层和驱动层分析)

一、应用层测试c文件

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


// ledtest /dev/myled on
// ledtest /dev/myled off

int main(int argc, char **argv)
{
	int fd;
	char status = 0;
	
	if (argc != 3)
	{
		printf("Usage: %s <dev> <on|off>\n", argv[0]);
		printf("  eg: %s /dev/myled on\n",   argv[0]);
		printf("  eg: %s /dev/myled off\n", argv[0]);
		return -1;
	}
	// open
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("can not open %s\n", argv[0]);
		return -1;
	}

	// write
	if (strcmp(argv[2], "on") == 0)
	{
		status = 1;
	}

	write(fd, &status, 1);
	return 0;	
}

1、主函数剖析:

关于 int main(int argc, char argv) 参数argc 的理解
测试文件为 ledtest.c

a:

./ledtest /dev/100ask_led0 off   //关闭 led0 灯

这条指令 中  argc:3    argv[0] = ./ledtest
                      argv[1] = /dev/100ask_led0
                      argv[2] = off

b:

/mnt/ledtest /dev/myled on // 点灯
这条指令 中  argc:3    argv[0] = /mnt/ledtest
                      argv[1] = /dev/myled
                      argv[2] = on

c:

100ask_led0  和 myled 的理解

这是创建设备节点是名字的不同
	led_class = class_create(THIS_MODULE, "myled");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /* /dev/myled */

   led_class = class_create(THIS_MODULE, "100ask_led_class");
   device_create(led_class, NULL, MKDEV(major, i), NULL, "100ask_led%d", i); /* /dev/100ask_led0,1,... */
   

2、 open函数解析

关于 fd = open(argv[1], O_RDWR);的理解

open函数是Linux应用层访问驱动层的系统函数

(1)函数原型

int open(const char *path, int oflags,mode_t mode);

(2)函数说明

open建立了一条到文件或设备的访问路径。
open函数一般用于打开或者创建文件,在打开或创建文件时可以制定文件的属性及用户的权限等各种参数。
第一个参数path表示:路径名或者文件名。路径名为绝对路径名(如C:/cpp/a.cpp),文件则是在当前工作目录下的。
第二个参数oflags表示:打开文件所采取的动作。

(3)头文件

#include <sys/types.h>//这里提供类型pid_t和size_t的定义
#include <sys/stat.h>
#include <fcntl.h>

(4)标签

Flags: 
O_RDONLY 只读打开  
O_WRONLY 只写打开 
O_RDWR  可读可写打开

(5)返回值

open函数的返回值如果操作成功,它将返回一个文件描述符,如果操作失败,它将返回-1。

3、write 函数解析

(1)功能:

向文件中写入数据

(2)头文件:

#include  <unistd.h>

(3)原型:

ssize_t write(int fd, const void *buf, size_t count);

(4)参数:

fd: 文件描述符
buf: 存放要写入的数据的缓冲区首地址
count: 想要写入的字节数

(5)返回值:

=0:成功写入的字节数,0表示什么都没写入
-1: 写入失败,并设置全局变量errno

二、LED 驱动程序

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

#include "led_opr.h"

#define LED_NUM 2

/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;


#define MIN(a, b) (a < b ? a : b)

/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode *inode = file_inode(file);
	int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	p_led_opr->ctl(minor, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	p_led_opr->init(minor);
	
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
	int err;
	int i;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */


	led_class = class_create(THIS_MODULE, "100ask_led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_led");
		return -1;
	}

	for (i = 0; i < LED_NUM; i++)
		device_create(led_class, NULL, MKDEV(major, i), NULL, "100ask_led%d", i); /* /dev/100ask_led0,1,... */

	p_led_opr = get_board_led_opr();
	
	return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit led_exit(void)
{
	int i;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	for (i = 0; i < LED_NUM; i++)
		device_destroy(led_class, MKDEV(major, i)); /* /dev/100ask_led0,1,... */

	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

1、驱动程序解析

(1)定义功能函数

led_drv_read
led_drv_write
led_drv_open
led_drv_close

我们写的程序针对硬件部分抽象出 led_operations 结构体(驱动层和硬件层对接的抽象结构体)

struct led_operations {
	int num;
	int (*init) (int which); /* 初始化LED, which-哪个LED */       
	int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
};

(2)定义自己的file_operations结构体 ,并且将功能函数注册到结构体(面向对象方式:通过函数指针进行封装)

Linux 中#include <linux/fs.h> 中file_operations 原型

   struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);
	ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
			u64);
};       
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

(3)定义入口函数和出口函数

入口函数:安装驱动程序时,就会去调用这个入口函数
卸载驱动程序时,就会去调用这个出口函数

创建设备节点

module_init(led_init);
module_exit(led_exit);

三、应用程序和驱动程序的相互对接

应用层序 的 write、 open函数通过函数指针的方式在驱动层分别对接led_drv_write 和led_drv_write函数。也就是驱动层的函数把自己的实体注册到了应用层提供的接口,这样就可以通过应用层访问到驱动层,实现了应用和驱动的分层设计,Linux 处处体现着面向对象的编程方法。

static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};
(1)copy_from_user,用来将数据从用户空间复制到内核空间  (write)
(2)copy_to_user,用来将数据从内核空间复制到用户空间    (read)
(3)字符设备驱动程序抽象出一个 file_operations 结构体;(应用层 write 和驱动层对接的抽象结构体)
(4)我们写的程序针对硬件部分抽象出 led_operations 结构体(驱动层和硬件层对接的抽象结构体)

相关文章:

  • 猿创征文 | [云原生]为微服务保驾护航之链路跟踪skywalking保姆级搭建教程
  • 雅思口语高分课程
  • java面向对象面试题的考点整理
  • Mac 下 brew 切换为国内源,安装 sshfs
  • 【明年找到好工作】:面试题打卡第三天
  • 网课搜题公众号接口 大学生新手使用必备
  • Github操作—团队内协作(四)——Git
  • redis集群模式详解
  • VMware——虚拟机的创建(Linux)
  • 读书记:认知觉醒(三)元认知、自控力
  • 中缀表达式转后缀表达式,及含多位负数的中缀表达式计算(中缀转后缀解法)
  • 电商通用(四)
  • 尾插建立单链表,C语言输出
  • 对象映射的那些事儿及MapStruct入门
  • 基于SSM框架的杰森摄影工作室选片系统的设计和开发论文
  • Android系统模拟器绘制实现概述
  • axios 和 cookie 的那些事
  • django开发-定时任务的使用
  • E-HPC支持多队列管理和自动伸缩
  • Flannel解读
  • Linux CTF 逆向入门
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • Promise面试题,控制异步流程
  • python学习笔记-类对象的信息
  • underscore源码剖析之整体架构
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • 猴子数据域名防封接口降低小说被封的风险
  • 基于webpack 的 vue 多页架构
  • 今年的LC3大会没了?
  • 前端面试之闭包
  • 日剧·日综资源集合(建议收藏)
  • 使用parted解决大于2T的磁盘分区
  • 使用Swoole加速Laravel(正式环境中)
  • 怎么将电脑中的声音录制成WAV格式
  • 中文输入法与React文本输入框的问题与解决方案
  • Java总结 - String - 这篇请使劲喷我
  • kubernetes资源对象--ingress
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • (10)STL算法之搜索(二) 二分查找
  • (12)Hive调优——count distinct去重优化
  • (java)关于Thread的挂起和恢复
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (理论篇)httpmoudle和httphandler一览
  • (四) Graphivz 颜色选择
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .net core 源码_ASP.NET Core之Identity源码学习