系列文章目录

第一章 Linux入门之驱动框架
第二章 Linux驱动之字符设备驱动


文章目录

  • 系列文章目录
  • 前言
  • 一、认识字符设备驱动
    • 1.基本概念
    • 2.基本概念
  • 二、字符设备旧框架
    • 1.注册和注销
    • 2.代码
    • 3.缺点
    • 4.实验用例
  • 三、新框架
    • 1.认识
    • 2.代码
    • 3. 驱动实验代码
  • 总结

前言

本章节简绍什么是Linux字符设备,认识字符设备的新旧框架,以及如何编写一个字符设备驱动


一、认识字符设备驱动

1.基本概念

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的 LED、按键、 IIC、 SPI, LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

2.基本概念

在详细的学习字符设备驱动架构之前,我们先来简单的了解一下 Linux 下的应用程序是如何调用驱动程序的, Linux 应用程序对驱动程序的调用如下图所示:

在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

比如现在有个叫做/dev/led 的驱动文件,此文件是 led 的驱动文件。应用程序使用 open 函数来打开文件/dev/led,使用完成以后使用 close 函数关闭/dev/led 这个文件。 open 和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭 led,那么就使用 write 函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开led 的控制参数。如果要获取 led 灯的状态,就用 read 函数从驱动中读取相应的状态。

应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间陷入到内核空间,这样才能实现对底层驱动的操作。 open、 close、 write 和 read 等这些函数是有 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。当我们调用 open 函数的时候流程如下图所示:

其中关于 C 库以及如何通过系统调用陷入到内核空间这个我们不用去管,我们重点关注的是应用程序和具体的驱动,应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了 open这个函数,那么在驱动程序中也得有一个名为 open 的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux内核驱动操作函数集合

二、字符设备旧框架

1.注册和注销

字符设备的注册和注销使用下面的两个函数

//注册
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
//注销
static inline void unregister_chrdev(unsigned int major, const char *name)

参数说明
register_chrdev 函数用于注册字符设备,此函数一共有三个参数

  • major: 主设备号, Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分
  • name:设备名字,指向一串字符串。
  • fops: 结构体 file_operations 类型指针,指向设备的操作函数集合变量。

unregister_chrdev 函数用于注销字符设备,此函数有两个参数

  • major: 要注销的设备对应的主设备号。
  • name: 要注销的设备对应的设备名

2.代码

#include <linux/init.h>
#include <linux/module.h>static struct file_operations test_fops;/* 驱动入口函数 */
static int __init xxx_init(void)
{/* 入口函数具体内容 */int retvalue = 0;/* 注册字符设备驱动 */retvalue = register_chrdev(200, "chrtest", &test_fops);if(retvalue < 0){/* 字符设备注册失败,自行处理 */}return 0;
}/* 驱动出口函数 */
static void __exit xxx_exit(void)
{/* 注销字符设备驱动 */unregister_chrdev(200, "chrtest");
}/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

3.缺点

  1. 自己手动来分配设备号,可能会出现设备号的重复使用的问题, 可先使用命令cat /proc/devices来查看已经使用的设备号,然后在定义自己的设备号
  2. 驱动程序加载之后还需要手动来创建设备节点,创建成功可在/dev目录下查看
    mknod /dev/chrdevbase c 200 0
  3. 卸载设备驱动后,还需要设备来设备设备节点
    rm /dev/chrdevbase

4.实验用例

这里我使用的是ZYNQ AC7020核心板的一个LED字符设备驱动,才用地址映射的方式来操作IO口

驱动程序

/***************************************************************
文件名 : led.c
作者 : oldzhai
版本 : V1.0
描述 : ZYNQ LED 驱动文件。
其他 : LED为PS MIO0
***************************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 *//** GPIO 相关寄存器地址定义*/
#define ZYNQ_GPIO_REG_BASE 0xE000A000
#define DATA_OFFSET        0x00000040
#define DIRM_OFFSET        0x00000204
#define OUTEN_OFFSET       0x00000208
#define INTDIS_OFFSET      0x00000214
#define APER_CLK_CTRL      0xF800012C/* 映射后的寄存器虚拟地址指针 */
static void __iomem *data_addr;
static void __iomem *dirm_addr;
static void __iomem *outen_addr;
static void __iomem *intdis_addr;
static void __iomem *aper_clk_ctrl_addr;/** @description : 打开设备* @param – inode : 传递给驱动的 inode* @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量* 一般在 open 的时候将 private_data 指向设备结构体。* @return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{return 0;
}/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{return 0;
}/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{int ret;int val;char kern_buf[1];ret = copy_from_user(kern_buf, buf, cnt);// 得到应用层传递过来的数据if(0 > ret) {printk(KERN_ERR "kernel write failed!\r\n");return -EFAULT;}val = readl(data_addr);if (0 == kern_buf[0])val &= ~(0x1U); // 如果传递过来的数据是 0 则关闭 ledelse if (1 == kern_buf[0])val |= (0x1U); writel(val, data_addr);return 0;
}/*
* @description : 关闭/释放设备
* @param – filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};static int __init led_init(void)
{u32 val;int ret;/* 1.寄存器地址映射 */data_addr = ioremap(ZYNQ_GPIO_REG_BASE + DATA_OFFSET, 4);dirm_addr = ioremap(ZYNQ_GPIO_REG_BASE + DIRM_OFFSET, 4);outen_addr = ioremap(ZYNQ_GPIO_REG_BASE + OUTEN_OFFSET, 4);intdis_addr = ioremap(ZYNQ_GPIO_REG_BASE + INTDIS_OFFSET, 4);aper_clk_ctrl_addr = ioremap(APER_CLK_CTRL, 4);/* 2.使能 GPIO 时钟 */val = readl(aper_clk_ctrl_addr);val |= (0x1U << 22);writel(val, aper_clk_ctrl_addr);/* 3.关闭中断功能 */val |= (0x1U);writel(val, intdis_addr);/* 4.设置 GPIO 为输出功能 相应bit为为1 */val = readl(dirm_addr);val |= (0x1U);writel(val, dirm_addr);/* 5.使能 GPIO 输出功能 */val = readl(outen_addr);val |= (0x1U);writel(val, outen_addr);/* 6.默认关闭 LED */val = readl(data_addr);val &= ~(0x1U);writel(val, data_addr);/* 7.注册字符设备驱动 */ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);if(0 > ret){printk(KERN_ERR "Register LED driver failed!\r\n");return ret;}return 0;
}static void __exit led_exit(void)
{/* 1.卸载设备 */unregister_chrdev(LED_MAJOR, LED_NAME);/* 2.取消内存映射 */iounmap(data_addr);iounmap(dirm_addr);iounmap(outen_addr);iounmap(intdis_addr);iounmap(aper_clk_ctrl_addr);
}/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);MODULE_AUTHOR("oldzhai <zfq0620@126.com>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

应用程序

/***************************************************************
文件名 : ledApp.c
版本 : V1.0
描述 : LED 驱测试 APP。
其他 : 无
使用方法 : ./ledApp /dev/led 0 关闭 LED ./ledApp /dev/led 1 打开 LED
***************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>/** @description : main 主程序* @param - argc : argv 数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, ret;unsigned char buf[1];if(3 != argc) {printf("Usage:\n""\t./ledApp /dev/led 1 @ close LED\n""\t./ledApp /dev/led 0 @ open LED\n");return -1;}/* 打开设备 */fd = open(argv[1], O_RDWR);if(0 > fd) {printf("file %s open failed!\r\n", argv[1]);return -1;}/* 将字符串转换为 int 型数据 */buf[0] = atoi(argv[2]);/* 向驱动写入数据 */ret = write(fd, buf, sizeof(buf));if(0 > ret){printf("LED Control Failed!\r\n");close(fd);return -1;}/* 关闭设备 */close(fd);return 0;
}

三、新框架

1.认识

加载模块的内容

  1. 申请设备号 主设备号和次设备号
  2. struct cdev 结构体
  3. cdev 需要和设备号关联
  4. cdev 需要和操作方法集关联
  5. 将cdev加载到内核
  6. 读设备树(可无)
  7. 地址映射(可无)
  8. 配置寄存器(可无)
  9. 创建一类设备class_device
  10. 创建这类设备的设备节点

删除设备节点

  1. 删除设备类
  2. 解除地址映射
  3. 删除cdev
  4. 释放设备号

a. 设备号的操作

//设备号的申请函数
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
//给定了设备的主设备号和次设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
//释放掉设备号
void unregister_chrdev_region(dev_t from, unsigned count)

b. 设备注册方法
在 Linux 中使用 cdev 结构体表示一个字符设备, cdev 结构体在 include/linux/cdev.h 文件中的定义如下

 struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};

在 cdev 中有两个重要的成员变量: ops 和 dev,这两个就是字符设备文件操作函数集合 file_operations以及设备号 dev_t。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个变量就表示一个字符设备,如下所示:struct cdev test_cdev;

定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化

//参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)

向 Linux 系统添加这个字符设备

//参数 p 指向要添加的字符设备(cdev 结构体变量),参数 dev 就是设备所使用的设备号,参数 count 是要添加的设备数量
int cdev_add(struct cdev *p, dev_t dev, unsigned count)

从 Linux 内核中删除相应的字符设备

//参数 p 指向要添加的字符设备(cdev 结构体变量)
cdev_del(struct cdev *p)

c. 自动创建设备节点

//创建设备类
struct class *class_create (struct module *owner, const char *name)
//删除掉类
void class_destroy(struct class *cls);//创建设备
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
//删除设备 参数 classs 是要删除的设备所处的类,参数 devt 是要删除的设备号。
void device_destroy(struct class *class, dev_t devt)

2.代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>#define DEVICE_NUM 1
#define API()   printk(KERN_INFO "func:%s, is called \r\n", __FUNCTION__ );static int major = 0;
static int minor = 0;
static struct cdev cdev; int drv_open (struct inode * inode, struct file * file)
{API();//设备号//printk(KERN_INFO "major=%d, minor=%d\r\n", MAJOR(inode->i_rdev), MINOR(inode->i_rdev));return 0;
}int drv_close (struct inode * inode, struct file * file)
{API();return 0;
}ssize_t drv_read (struct file * filp, char __user * user_buffer, size_t size, loff_t * pPos)
{API();return 0;
}ssize_t drv_write (struct file * filp, const char __user * user_buffer, size_t size, loff_t * pPos)
{API();return 0;
}//定义了一个操作文件集
static struct file_operations fops = {.owner = THIS_MODULE,.read = drv_read,.write = drv_write,.open = drv_open,.release = drv_close,
};int __init hello_init( void )
{int ret = 0;dev_t dev;printk("-------------hello_init--------------\r\n");//申请设备号if ( major ){//静态申请dev = MKDEV(major, minor);ret = register_chrdev_region(dev, DEVICE_NUM, "hello_device");}else {/* 动态申请设备号 */ret = alloc_chrdev_region( &dev, minor, DEVICE_NUM, "hello_device2");}if ( ret ){printk(KERN_ERR "register chardev region error\r\n" );goto err_register;}major = MAJOR(dev); /* 获取分配号的主设备号 */minor = MINOR(dev); /* 获取分配号的次设备号 *///cdev 和操作方法集关联cdev_init(&cdev, &fops);cdev.owner = THIS_MODULE;//cdev 和设备号及设备个数关联,并且 加载到内核ret = cdev_add(&cdev, dev, DEVICE_NUM);if ( ret < 0 ){printk(KERN_ERR "cdev add error\r\n");goto err_add;}//读取设备树//映射//配置//创建设备类//创建设备节点printk(KERN_INFO "major=%d, minor=%d\r\n", major, minor);return 0;
err_add://释放设备号unregister_chrdev_region(dev, DEVICE_NUM);
err_register:return ret;
}void __exit hello_exit(void)
{dev_t dev;printk("-------------hello_exit-------------\r\n");dev = MKDEV(major, minor);//删除所有的设备节点//删除设备类//解除内存映射//删除cdevcdev_del(&cdev);//释放设备号unregister_chrdev_region(dev, DEVICE_NUM);
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ruth Wei <weij_xa@hqyj.com>");
MODULE_DESCRIPTION("this is test for useSymbol driver @bug1101");
MODULE_VERSION("1.0.0.1");

3. 驱动实验代码

驱动程序

/***************************************************************
文件名 : newchrled.c
版本 : V1.0
描述 : ZYNQ LED 驱动文件。
其他 : LED管脚为PS MIO0 低电平点亮
日志 : 初版 V1.0
***************************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>#define NEWCHRLED_CNT 1 /* 设备号个数 */
#define NEWCHRLED_NAME "newchrled" /* 名字 *//*
* GPIO 相关寄存器地址定义
*/
#define ZYNQ_GPIO_REG_BASE 0xE000A000
#define DATA_OFFSET        0x00000040
#define DIRM_OFFSET        0x00000204
#define OUTEN_OFFSET       0x00000208
#define INTDIS_OFFSET      0x00000214
#define APER_CLK_CTRL      0xF800012C/* 映射后的寄存器虚拟地址指针 */
static void __iomem *data_addr;
static void __iomem *dirm_addr;
static void __iomem *outen_addr;
static void __iomem *intdis_addr;
static void __iomem *aper_clk_ctrl_addr;/*newchrled 设备结构体 */
struct newchrled_dev {dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */
};static struct newchrled_dev newchrled; /* led 设备 *//*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &newchrled; /* 设置私有数据 */return 0;
}/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{return 0;
}/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{int ret;int val;char kern_buf[1];ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据if(0 > ret) {printk(KERN_ERR "kernel write failed!\r\n");return -EFAULT;}val = readl(data_addr);if (0 == kern_buf[0])val &= ~(0x1U); // 如果传递过来的数据是 0 则点亮 ledelse if (1 == kern_buf[0])val |= (0x1U);  // 如果传递过来的数据是 1 则关闭 ledwritel(val, data_addr);return 0;
}/*
* @description : 关闭/释放设备
* @param – filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}static inline void led_ioremap(void)
{data_addr = ioremap(ZYNQ_GPIO_REG_BASE + DATA_OFFSET, 4);dirm_addr = ioremap(ZYNQ_GPIO_REG_BASE + DIRM_OFFSET, 4);outen_addr = ioremap(ZYNQ_GPIO_REG_BASE + OUTEN_OFFSET, 4);intdis_addr = ioremap(ZYNQ_GPIO_REG_BASE + INTDIS_OFFSET, 4);aper_clk_ctrl_addr = ioremap(APER_CLK_CTRL, 4);
}static inline void led_iounmap(void)
{iounmap(data_addr);iounmap(dirm_addr);iounmap(outen_addr);iounmap(intdis_addr);iounmap(aper_clk_ctrl_addr);
}/* 设备操作函数 */
static struct file_operations newchrled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};static int __init led_init(void)
{u32 val;int ret;/* 1.寄存器地址映射 */led_ioremap();/* 2.使能 GPIO 时钟 U表示无符号unsigned*/ val = readl(aper_clk_ctrl_addr);val |= (0x1U << 22);writel(val, aper_clk_ctrl_addr);/* 3.关闭中断功能 */val |= (0x1U);writel(val, intdis_addr);/* 4.设置 GPIO 为输出功能 */val = readl(dirm_addr);val |= (0x1U);writel(val, dirm_addr);/* 5.使能 GPIO 输出功能 */val = readl(outen_addr);val |= (0x1U);writel(val, outen_addr);/* 6.默认关闭 LED  高电平1关闭*/val = readl(data_addr);val |= (0x1U);writel(val, data_addr);/* 7.注册字符设备驱动 *//* 创建设备号 */if (newchrled.major) {newchrled.devid = MKDEV(newchrled.major, 0);ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT,NEWCHRLED_NAME);if (ret)goto out1;} else {ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT,NEWCHRLED_NAME);if (ret)goto out1;newchrled.major = MAJOR(newchrled.devid);newchrled.minor = MINOR(newchrled.devid);}printk("newcheled major=%d,minor=%d\r\n",newchrled.major,newchrled.minor);/* 初始化 cdev */newchrled.cdev.owner = THIS_MODULE;cdev_init(&newchrled.cdev, &newchrled_fops);/* 添加一个 cdev */ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);if (ret)goto out2;/* 创建类 */newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);if (IS_ERR(newchrled.class)) {ret = PTR_ERR(newchrled.class);goto out3;}/* 创建设备 */newchrled.device = device_create(newchrled.class, NULL,newchrled.devid, NULL, NEWCHRLED_NAME);if (IS_ERR(newchrled.device)) {ret = PTR_ERR(newchrled.device);goto out4;}return 0;out4:class_destroy(newchrled.class);out3:cdev_del(&newchrled.cdev);out2:unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);out1:led_iounmap();return ret;
}static void __exit led_exit(void)
{/* 注销设备 */device_destroy(newchrled.class, newchrled.devid);/* 注销类 */class_destroy(newchrled.class);/* 删除 cdev */cdev_del(&newchrled.cdev);/* 注销设备号 */unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);/* 取消地址映射 */led_iounmap();
}/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);MODULE_AUTHOR("OLDZHAI <zfq0620@126.com>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

应用程序

/***************************************************************
文件名 : led-app.c
版本 : V1.0
描述 : LED 驱测试 APP。
其他 : 无
使用方法 : ./ledApp /dev/led 0 关闭 LED ./ledApp /dev/led 1 打开 LED
日志 : 初版 V1.0
***************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>/** @description : main 主程序* @param - argc : argv 数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, ret;unsigned char buf[1];if(3 != argc) {printf("Usage:\n""\t./ledApp /dev/led 1 @ close LED\n""\t./ledApp /dev/led 0 @ open LED\n");return -1;}/* 打开设备 */fd = open(argv[1], O_RDWR);if(0 > fd) {printf("file %s open failed!\r\n", argv[1]);return -1;}/* 将字符串转换为 int 型数据 */buf[0] = atoi(argv[2]);/* 向驱动写入数据 */ret = write(fd, buf, sizeof(buf));if(0 > ret){printf("LED Control Failed!\r\n");close(fd);return -1;}/* 关闭设备 */close(fd);return 0;
}

总结

现在使用的是新字符设备的框架,旧的框架只是为了更好的理解字符设备驱动

Linux驱动之字符设备驱动相关推荐

  1. linux的驱动开发——字符设备驱动

    1.字符设备驱动 \qquad字符设备驱动是最基本,最常用的设备.它将千差万别的硬件设备采用统一的接口封装起来,屏蔽了硬件的差异,简化了应用层的操作. 2.描述所有字符设备的结构体 \qquad描述所 ...

  2. linux PCI驱动调用字符设备驱动方式

    上一篇文章写了字符设备驱动的基本结构及访问方式,在实际应用时首先需要绑定自己的硬件设备.本篇主要描述字符设备驱动与PCI接口类型的设备访问方式(内核为2.6.24及以上的方法,测试内核为2.6.32) ...

  3. <Linux开发>--驱动开发-- 字符设备驱动(3) 过程详细记录

    <Linux开发>–驱动开发-- 字符设备驱动(3) 过程详细记录 驱动开发是建立再系统之上的,前面作者也记录了系统移植的过程记录,如果有兴趣,可进入博主的主页查看相关文章,这里就不添加链 ...

  4. 【linux驱动之字符设备驱动基础】

    linux驱动之字符设备驱动基础 文章目录 linux驱动之字符设备驱动基础 前言 一.开启驱动学习之路 二.驱动预备知识 三.什么是驱动? 3.1 驱动概念 3.2 linux 体系架构 3.3 模 ...

  5. linux用户空间flash驱动,全面掌握Linux驱动框架——字符设备驱动、I2C驱动、总线设备驱动、NAND FLASH驱动...

    原标题:全面掌握Linux驱动框架--字符设备驱动.I2C驱动.总线设备驱动.NAND FLASH驱动 字符设备驱动 哈~ 这几天都在发图,通过这种方式,我们希望能帮大家梳理学过的知识,全局的掌握Li ...

  6. 设备驱动,字符设备驱动、(总线)设备驱动模型、sysfs文件系统、平台设备驱动

    以下内容转载于微信公众号:嵌入式企鹅圈.如有侵权,请告知删除. 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术. 对于初学者来说会非常 ...

  7. 正点原子-驱动开发-字符设备驱动

    Linux中的三大类驱动:字符设备.块和网络设备驱动 I2C.SPI.音频等都属于字符设备驱动 的类型 EMMC.NAND.SD卡和 U盘等存储都属于块设备 网卡,WIFI等都属于网络驱动 一个设备可 ...

  8. 深入浅出:Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  9. 【Linux驱动】字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...

最新文章

  1. 【pytorch】nn.LSTM的使用
  2. MaperReduce实验
  3. DDNS 的工作原理及其在 Linux 上的实现--转
  4. Py之pixellib:pixellib库的简介、安装、经典案例之详细攻略
  5. WCF从理论到实践(4):路在何方
  6. matlab数据点降噪,matlab学习之降噪平滑算法
  7. mysql函数commit_mysql的函数不能用commit吗?
  8. Python学习:3.Python学习基础
  9. QA视角看数据匿名化
  10. 交换机日志删除_锐捷交换机记录日志到flash功能详解 | 19号系统
  11. Matlab实现图像识别(一)
  12. 计算机二级C语言选择题——01
  13. 【成功解决】Ubuntu下U盘文件夹不存在
  14. Egyptian Miracle Technical Service Support
  15. 520情人节告白❤HTML+CSS+JavaScript实现抖音流动爱心
  16. 邮件解析引擎FastMail库使用
  17. 文秘要学计算机吗,高考志愿:计算机专业和文秘专业哪个适合女生?
  18. AppleScript 实现 imessage 批量推送 苹果推 (亲测通过)
  19. SAP GUI 登录语言设置
  20. PyHook3详细教程

热门文章

  1. 网络相关面试题(持续更新)
  2. 云计算:程序员重回个人英雄时代,国内云计算平台即将搭建运行。
  3. laravel-debugbar chrome浏览器样式错乱问题
  4. 德国西克SICK编码器调零工具,伺服电机维修调试工具
  5. Mondrian异常
  6. 迅雷导入未完成下载失败
  7. 听说你们的数据库并发 2 万就跪了?
  8. win10怎样锁定计算机,win10
  9. Python调用ffmpeg对视频抽帧
  10. pdf文档解析相关工具包