字符设备的编写二

简介: 字符设备的编写二

前言

上一篇文章介绍了一下字符设备编写的步骤,仅此而已,想要真正掌握字符设备编写,需要对其包含的数据结构有所掌握,这才是重要的。


一、字符设备的数据结构

图片来源于网络,一直以来有想自己画一下,以后有时间将上图换成我自己画的。

二、数据结构分析

编写字符设备需要实现file_operations接口,在APP打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于 APP的每一个文件句柄,在内核里面都有一个“ struct file”与之对应。

struct file {
  union {
    struct llist_node fu_llist;
    struct rcu_head   fu_rcuhead;
  } f_u;
  struct path   f_path;
#define f_dentry  f_path.dentry
  struct inode    *f_inode; /* cached value */
  const struct file_operations  *f_op;
  /*
   * Protects f_ep_links, f_flags.
   * Must not be taken from IRQ context.
   */
  spinlock_t    f_lock;
  atomic_long_t   f_count;
  unsigned int    f_flags;
  fmode_t     f_mode;
  struct mutex    f_pos_lock;
  loff_t      f_pos;
  struct fown_struct  f_owner;
  const struct cred *f_cred;
  struct file_ra_state  f_ra;

open 打开文件时,传入的 flags、 mode 等参数会被记

录在内核中对应的 struct file 结构体里(f_flags,f_mode)去读写文件时,文件的当前偏移地址也会保存在 struct file 结构体的f_pos 成员里。打开字符设备节点时,内核中也有对应的 struct file。这个结构体中的结构体:

struct file_operations *f_op;

这是由驱动程序提供的。


总结

字符设备就暂时学习到这,后面将继续为大家分享。

附录

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE        (0X020C406C)  
#define SW_MUX_GPIO1_IO03_BASE    (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE    (0X020E02F4)
#define GPIO1_DR_BASE       (0X0209C000)
#define GPIO1_GDIR_BASE       (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static volatile unsigned int  *IMX6U_CCM_CCGR1;
static volatile unsigned int  *SW_MUX_GPIO1_IO03;
static volatile unsigned int  *SW_PAD_GPIO1_IO03;
static volatile unsigned int  *GPIO1_DR;
static volatile unsigned int  *GPIO1_GDIR;
//静态指定
static  unsigned int dev_major;
static  struct class *cls;
static  struct device *dev;
  /* 映射后的寄存器虚拟地址指针 */
static int kernel_val = 555;
//read(fd,buf,size);
ssize_t chr_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
  int ret;
  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  ret = copy_to_user(buf, &kernel_val, count);
  if(ret > 0)
  {
    printk("failed copy_to_user\r\n");
    return -EFAULT;
  }
  return 0;
}
ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
  int ret;
  int value;
  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  ret = copy_from_user(&value, buf, count);
  if(ret > 0)
  {
    printk("failed copy_from_user\r\n");
    return -EFAULT;
  }
  if(value){
    *GPIO1_DR |= (1 << 3);
  }else{
    *GPIO1_DR &= ~(1 << 3);
  }
  return 0;
}
int chr_drv_open (struct inode *inode, struct file *filp)
{
  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  return 0;
}
int chr_drv_close (struct inode *inode, struct file *filp)
{
  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  return 0;
}
const struct file_operations my_fops  = {
  .open = chr_drv_open,
  .read = chr_drv_read,
  .write = chr_drv_write,
  .release = chr_drv_close,
};
/*
②在模块加载入口函数中
  a  申请主设备号(内核中用于区分和管理不同字符设备)
    register_chrdev()
  b  创建设备节点文件(为用户提供一个可操作到文件接口--open())
    struct class *class_create()
    struct device *device)create()
  c  硬件初始化
    1.地址映射
    2.中断申请
    3.实现硬件的寄存器的初始化
  d  实现file_operation
*/
static int __init chr_dev_init(void)
{
  int ret;
/*
  //0 实例化全局的设备对象
  led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
  if(led_dev == NULL);
  {
    printk("kmalloc failed\r\n");
    return -ENOMEM;
  }
*/
  //一般都是申请设备号资源
  //1.申请设备号
  dev_major = register_chrdev(0, "chr_dev_test", &my_fops);
  if(dev_major < 0){
    printk(KERN_ERR"register_chrdev failed\n");
    return  -ENODEV;
  }
  //2.创建设备文件
  cls = class_create(THIS_MODULE, "chr_cls");
  if(IS_ERR(cls))
  {
    printk(KERN_ERR"class_create failed\n");
    ret = PTR_ERR(cls);//将指针出错原因转换为一个出错码
    goto err_1;
  }
  // /dev/led0
  dev = device_create(cls, NULL, MKDEV(dev_major, 0), NULL, "led%d", 0);
  if(IS_ERR(dev))
  {
    printk(KERN_ERR"device_create failed\n");
    ret = PTR_ERR(dev);//将指针出错原因转换为一个出错码
    goto err_2;
  }
  //3,硬件初始化
    /* 初始化LED */
  /* 1、寄存器地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
  SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
  GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
  GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
  /* enable GPIO1
   * configure GPIO1_io3 as gpio
   * configure GPIO1_io3 as output 
   */
  *IMX6U_CCM_CCGR1 |= 0x0C000000;
  *SW_MUX_GPIO1_IO03 &= 0xfffffff0;
  *SW_MUX_GPIO1_IO03 |= 0x5;
  *SW_PAD_GPIO1_IO03 &= 0xfffffff0;
  *SW_PAD_GPIO1_IO03 |=  0x10B0;
  *GPIO1_GDIR &= 0xfffffff0;
  *GPIO1_GDIR |= 0x8;
/*
readl   writel
static inline u32 readl(const volatile void __iomem *addr)
static inline void writel(u32 value, void *addr)
 2、使能GPIO1时钟 
  val = readl(IMX6U_CCM_CCGR1);
  val &= ~(3 << 26);   清楚以前的设置 
  val |= (3 << 26); 设置新值 
  writel(val, IMX6U_CCM_CCGR1);
   3、设置GPIO1_IO03的复用功能,将其复用为
      GPIO1_IO03,最后设置IO属性。
  writel(5, SW_MUX_GPIO1_IO03);
  寄存器SW_PAD_GPIO1_IO03设置IO属性
   bit 16:0 HYS关闭
   bit [15:14]: 00 默认下拉
     bit [13]: 0 kepper功能
     bit [12]: 1 pull/keeper使能
     bit [11]: 0 关闭开路输出
     bit [7:6]: 10 速度100Mhz
     bit [5:3]: 110 R0/6驱动能力
     bit [0]: 0 低转换率
  writel(0x10B0, SW_PAD_GPIO1_IO03);
   4、设置GPIO1_IO03为输出功能 
  val = readl(GPIO1_GDIR);
  val &= ~(1 << 3);  清除以前的设置 
  val |= (1 << 3);   设置为输出 
  writel(val, GPIO1_GDIR);
   5、默认关闭LED 
  val = readl(GPIO1_DR);
  val |= (1 << 3);  
  writel(val, GPIO1_DR);
writel(readl(led_dev->GPIO1_DR) | (1 << 3),led_dev->GPIO1_DR);
*/
  return 0;
err_2:
  class_destroy(cls);
err_1:
  unregister_chrdev(dev_major, "chr_dev_test");
  return ret;
}
static void __exit chr_dev_exit(void)
{
  //一般都是释放资源
    /* 取消映射 */
  iounmap(IMX6U_CCM_CCGR1);
  iounmap(SW_MUX_GPIO1_IO03);
  iounmap(SW_PAD_GPIO1_IO03);
  iounmap(GPIO1_DR);
  iounmap(GPIO1_GDIR);
  device_destroy(cls, MKDEV(dev_major, 0));
  class_destroy(cls);
  unregister_chrdev(dev_major, "chr_dev_test");
}
//①实现模块的加载和卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lfp");


目录
相关文章
|
小程序 Shell Linux
workman(二)thinkphp5.0安装websocket插件workerman
首先说明一下我使用的PHP框架是thinkphp5.0。 当然,workerman这个插件不是只有thinkphp5.0可以使用。 具体的安装方法,thinkphp5.0的官方手册中是给出了明确的说明 请移步《thinkphp5.0官方手册》
703 0
|
对象存储
阿里云OSS 服务端签名后直传之分片上传(结合element-ui的upload组件)
阿里云OSS 服务端签名后直传之分片上传(结合element-ui的upload组件)
1141 0
|
算法 数据可视化 数据挖掘
算法金 | 一个强大的算法模型:t-SNE !!
**t-SNE算法简介** t-SNE(t-Distributed Stochastic Neighbor Embedding)是一种非线性降维技术,用于高维数据的可视化和结构保留。它通过保持高维数据点间的局部相似性来创建低维表示,尤其适用于揭示复杂数据集的模式。算法的关键在于使用高斯分布计算高维相似性,而用t分布计算低维相似性,并通过最小化Kullback-Leibler散度来优化低维空间的位置。t-SNE在图像、文本和生物信息学等领域有广泛应用,但计算复杂度高,不适合大规模数据。
686 6
算法金 | 一个强大的算法模型:t-SNE !!
|
Java API 应用服务中间件
Java一分钟之-JNDI:Java命名和目录接口
【6月更文挑战第3天】JNDI,Java Naming and Directory Interface,是Java的API,用于访问命名和目录服务。本文聚焦JNDI常见问题和易错点:1) 初始化Context时需正确配置环境属性;2) 查找资源时确保名称与服务器配置一致;3) 别忘了导入JNDI库和妥善处理异常;4) 使用后记得关闭资源。理解JNDI并避免这些问题能提升应用的可维护性和灵活性。
640 2
|
编解码 网络协议 Java
netty有什么优势
Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它的主要特点包括:开发门槛低, 定制能力强 ,高性能,活跃的社区.
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用合集之mysql cdc支持全量的时候并发读取,该如何配置
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
JavaScript 数据安全/隐私保护 Python
东方财富股票数据JS逆向:secids字段和AES加密实战
东方财富股票数据JS逆向:secids字段和AES加密实战
1005 0
|
分布式计算 Hadoop Linux
找到Hadoop的安装目录
【4月更文挑战第19天】具体的安装目录可能因您的安装方式和环境而有所不同。如果您在安装Hadoop时遵循了特定的教程或文档,建议参考该教程或文档中的安装目录信息。
744 3
|
JavaScript
Vue3 循环渲染 v-for
Vue3 循环渲染 v-for
451 1
|
移动开发 小程序 API
【每周一个小技巧】支付宝小程序内如何跳转生活号文章
【每周一个小技巧】支付宝小程序内如何跳转生活号文章
870 8

热门文章

最新文章