字符设备驱动程序编写基础

编写最简单的字符设备驱动 编写最简单的字符设备驱动1 编写驱动代码2 编写makefile3 编译和加载驱动4 编写应用程序测试驱动 1 编写驱动代码 驱动代码chardev.c如下: #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/wait.h> #include <linux/ 阅读详情

《字符设备驱动程序编写基础》

本文档的Copyleft归rosetta所有,使用GPL发布,可以自由拷贝、转载,转载时请保持文档的完整性。

参考资料:《Linux设备驱动程序 第三版》,scull源码,Linux内核源码
来源:http://blog.youkuaiyun.com/rosetta/article/details/7563606

  本文写了字符设备驱动编写的基础知识,以自己学习过程来记录,写的内容比较具体,也可以说是《Linux设备驱动程序》第三章字符设备驱动程序的读书笔记。
主要写怎么生成字符设备文件?字符设备文件怎么和内核模块挂钩?怎么编写一个简单的字符设备模块插入内核?怎么访问字符设备模块注册的函数?
适合入门级新手。
使用代码示例源于scull源码。

 1、基本概念
    *对字符设备的访问是通过文件系统的设备名称进行的,文件系统内的设备名称可通过ls /dev -l命令查看,第一列用"c"标识的为字符设备文件,
    即访问字符设备是通过访问字符设备驱动的设备文件达到的。
    *主设备号和次设备号
  ls /dev -al
  crw-r-----  1 root kmem     1,   1 04-29 11:57 mem
  crw-rw----  1 root audio   14,   2 04-29 11:58 midi
  crw-rw----  1 root audio   14,   0 04-29 11:58 mixer
  crw-r--r--  1 root root   252,   0 04-29 12:19 my_module
  crw-r--r--  1 root root   260,   1 04-29 12:20 my_module1
  其中的1,14,252,260为主设备号,1, 2, 0为次设备号。
  主设备号用来标识设备对应的驱动程序。
  而这些设备文件可由自行创建,以上的my_module使用252号驱动程序及my_module1使用260号驱动都是由我自己添加的(当然现我在这个驱动是假使的,是不存在的)
  添加字符设备文件命令:mknod  /dev/my_module1  c 260 1(添加后没有任何作用,需要注册后才和驱动挂钩起作用,一会会有实例)

  当前系统有哪些驱动程序可通过
  [root@xxx scull]# cat /proc/devices
  Character devices:
    1 mem
    4 /dev/vc/0
    4 tty
    4 ttyS
    5 /dev/tty
    5 /dev/console
    5 /dev/ptmx
    6 lp
    7 vcs
   10 misc
   13 input
   14 sound
   21 sg
   29 fb
  116 alsa
  128 ptm
  136 pts
  162 raw
  180 usb
  189 usb_device
  253 scull  //register_chrdev_region注册的。
  253 scullp
  253 sculla

  次设备号由内核使用,用于正确确定设备文件所指的设备,因为多个设备文件可使用同一驱动,但需要通过次设备号来区别,这样通过次设备号可以获得一个指向内核设备的指针,
  也可以理解为数组的索引,指针的偏移量。(次设备号具体有什么作用,没接触过这方面的代码,所以没什么体会)

2,在建立字符设备前,驱动程序需要首先获得设备号,可以指定一个未使用的设备号,也可以动态分配。
   当驱动程序获得了一个设备号后,怎么和设备文件联系起来呢?比如当前驱动程序获得的设备号为252
   那么可以使用类似mknod /dev/my_module c 252 0 来建立一个设备文件,驱动程序把此主设备号的设备文件向内核注册后即可使用。
 
  下面看实例:
  首先创建设备文件:
  mknod /dev/scull0 c 253 0 (scull源码目录下的scull/scull_load可自动生成)

  分配主从设备号
    int result, i;
    dev_t dev = 0;
  if (scull_major) {//主动指定主设备号
        dev = MKDEV(scull_major, scull_minor);//把主从设备号转换成dev_t类型
        result = register_chrdev_region(dev, scull_nr_devs, "scull");//最后一个参数“scull"就是cat /proc/devices显示的,如上面显示。
    } else {//如果不指定,则由内核自动分配主从设备号
        result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
                "scull");
        scull_major = MAJOR(dev);
    }
    if (result < 0) {
        printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
        return result;
    }

   分配了主从设备号后,怎么和刚才建立的设备文件/dev/scull0关联起来呢?那就需要向内核注册了
   注册的时候需要一个关键的结构体struct cdev,最关键的一个成员ops,cdev结构可以封闭在自已定义
  的结构体中,也可以直接就申请一个此结构体。看scull源码:
  struct scull_dev *scull_devices  //他的这个scull_dev就是自己定义的,里面包含有cdev结构体
  scull_setup_cdev(&scull_devices[i], i)
    static void scull_setup_cdev(struct scull_dev *dev, int index)
  {
      int err, devno = MKDEV(scull_major, scull_minor + index);
 
      cdev_init(&dev->cdev, &scull_fops);
      dev->cdev.owner = THIS_MODULE;//默认
      dev->cdev.ops = &scull_fops;//cdev结构体的ops在此初始化。
      err = cdev_add (&dev->cdev, devno, 1);//真正告诉内核我初始化化完成,即向内核注册。devno
      //对就的函数cdev_del从系统中移除一个设备。
      /* Fail gracefully if need be */
      if (err)
          printk(KERN_NOTICE "Error %d adding scull%d", err, index);
  }
    
  struct file_operations scull_fops = {//这个结构体中的东西才是核心啊!
      .owner =    THIS_MODULE,
      .llseek =   scull_llseek,
      .read =     scull_read,
      .write =    scull_write,
      .ioctl =    scull_ioctl,
      .open =     scull_open,
      .release =  scull_release,
  };

  因为单位历史因素,从一开始的2.4内核搞到现的2.6内核,所以还在延续使用老一设备接口,他们是:
  register_chrdev()
  和unregister_chrdev()

3,到目前为止一个简单的设备驱动模块就已经完成(完整源码请参考scull)。那么我们怎么对其操作和使用呢?
  可以直接到scull/scull/ make 再执行./scull_load,进行简单的测试
 
  读,通过cat /dev/scull进行
  写,可以通过ls -al > /dev/scull

  下面再写一个测试程序,演示用户层和内核层的读、写、ioctl操作。

  //其中的test.h是由scull.h改过来的。
  #include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
 
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <sys/ioctl.h>
  #include <linux/sysctl.h>
 
  #define TESTFILE "/dev/scull"
  #include "test.h"
 
  int main(int argc, char *argv[])
  {
      int fd = -1;
      fd = open(TESTFILE, O_RDWR);
      if(fd < 0)
      {
          printf("open error.\n");
          return -1;
      }
 
      char buf[30];
      if(strncmp(argv[1], "write", 5) == 0)//读和写都是比较简单的,也是容易理解。ioctl费了点神。
      {
          printf("write.\n");
          write(fd, "hello scull.\n", 20);
      }else if(strncmp(argv[1], "read", 4) == 0)
      {
          printf("read.\n");
          read(fd, buf, 20);
          printf("read buf:%s\n", buf);
      }else if(strncmp(argv[1], "ioctl", 4) == 0)
      {
          printf("ioctl.\n");
          int tmp = 111;//题外话,现在喜欢用这种风格,如果把局部变量都一起定义在开头固然整洁,但不方便看代码,还是什么时候用什么定义好。
          int ret = -1;
          if(ret = ioctl(fd, SCULL_IOCQQUANTUM, &tmp) < 0)//执行这个就是掉了内核里的scull_ioctl(),SCULL_IOCQQUANTUM
          {
              printf("ioctl error\n");
              return -1;
          }
          printf("ret:%d, tmp:x0%0x, %d\n", ret, &tmp, tmp);//可以在ko模块中把对应的值打出来对比
      }
 
      close(fd);
 
      return 0;
  }

  [root@xxx scull]# gcc test.c -Wall -g -o app


  //内核层ioctl,用户层的ioctl传进来时,cmd = SCULL_IOCQQUANTUM, arg = tmp(地址相同);
  int scull_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
 
    //截取部分内核层ioctl
     switch(cmd) {

      case SCULL_IOCRESET:
        scull_quantum = SCULL_QUANTUM;
        scull_qset = SCULL_QSET;
        break;

      case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        retval = __get_user(scull_quantum, (int __user *)arg);
        break;

      case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
        if (! capable (CAP_SYS_ADMIN))
            return -EPERM;
        scull_quantum = arg;
        break;

      case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
        retval = __put_user(scull_quantum, (int __user *)arg);
        break;

      case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
        {//自己改了下这个case分析。
        printk("SCULL_IOCQQUANTUM\n");//打印了此字符串。
        retval = __put_user(200, (int __user *)arg);//再把arg设为200.
        //return scull_quantum;
        return retval;
        }

  下面再帖一段调试结果:
  [root@xxx scull]# ./app write
  write.
  [root@xxx scull]# ./app read
  read.
  read buf:hello scull.
 
  [root@xxx scull]# ./app ioctl
  ioctl.
  ret:0, tmp:x0bfd01af0, 200
  [root@xxx scull]# dmesg
  in func:scull_open
  buf:hello scull.
 
  in func:scull_release
  in func:scull_open
  kernel read buf:hello scull.
 
  in func:scull_release
  in func:scull_open
  in func:scull_ioctl
  cmd :27399
  arg:0xbfd01af0
  _IOC_TYPE(cmd):107
  SCULL_IOC_MAGIC:107
  SCULL_IOCQQUANTUM
  in func:scull_release
  [root@panlimin scull]#

                         
  本人暂时也只能理解到这么多,至于ioctl能干什么?可以从内核获得一些数据这是必然的,
看书上说还可以对其进行一系列的控制,这当然肯定可以,这么多case分支,用户层选择适合自己
的cmd就可以完成对应的操作。除了这些,它还能干吗?或者说ioctl的用法? 除了read、write、
ioctl外,其它的比如mmap、poll操作实现等,内存共享、异步等原理的学习理解等都需要进一步
去研究学习。


高级OS(十四) - 编写字符设备驱动程序 高级OS(十四) - 编写字符设备驱动程序一.题目二.解答实验1.从应用程序的open(),read(),write()系统调用出发,说明如何陷入内核?2.分析3个file_operations()这个结构体中的成员含义3.应用程序如何对这个块设备进行读写,是通过ioctl还是其他方法?4.为什么要有主设备号和次设备号?5.驱动程序的注册和注销函数是做什么?6.simple_char_init方法的作用是什么? 一.题目 做实验楼的实验21(https://www.lanqiao.cn/courses/26 阅读详情

相关推荐

Linux_编译内核并编写一个最基础的字符设备驱动程序

linux内核编译以及字符设备驱动程序的编写 关键词: 内核编译; globalmem字符设备驱动编写; linux; linux设备驱动编写; 字符设备驱动程序主要内容 虚拟内存设备globalmem驱动实现,编写程序,然后将生成的驱动模块插入到驱动之中,接着编写测试程序,对设备globalmem进行测试。(Copyright © http://blog.youkuaiyun.com/s_...

S_gy_Zetrov的博客 8324

编写字符设备驱动程序

字符设备驱动也可以采用misc机制来进行注册,也就是Linux将一些不符合预先确定的字符设备划分为杂项设备,这类设备的主设备号是10,内核中使用miscdevice结构体来描述,如果使用misc机制来创建设备,就需要定义miscdevice结构体。linux的设备驱动程序可以由两种形式来定义,一种是全局静态变量,另一种是使用内核提供的API,这里采用第二种方法来实现一个简单的虚拟设备的驱动,并且实现它的读写功能。实现了对已注册的字符设备区间的管理,并完成与cdev的关联。打印出了open和read的方法。

qq_58538265的博客 1318

基于Linux的硬件驱动程序开发:深入探讨Linux驱动程序开发的基本知识!

🏆本文收录于「编程与技术实战」专栏,此专栏涵盖了C/C++编程、人工智能、数据结构、机器学习等技术领域的内容,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

**My Coding Family** 911

Linux内核学习10——编写字符设备驱动程序

一、 在linux内核中,字符设备是由cdev结构体来描述的,它位于/include/linux/cdev.h中 /* SPDX-License-Identifier: GPL-2.0 */ #ifndef _LINUX_CDEV_H #define _LINUX_CDEV_H #include <linux/kobject.h> #include <linux/kdev_t.h> #include <linux/list.h> #include <linux/

weixin_45730790的博客 3978

Linux 驱动开发基础知识——编写LED驱动程序(三)

我们基于 Hello 驱动程序先写出最简单的 LED 驱动程序

嵌入式技术开发 7513

RK3568平台开发系列讲解(基础篇)字符设备驱动关键结构体

Linux 内核中将字符设备抽象成一个具体的数据结构 (struct cdev), 我们可以理解为字符设备对象,cdev 记录了字符设备的相关信息 (设备号、内核对象),字符设备的打开、读写、关闭等操作接口(file_operations),在我们想要添加一个字符设备时,就是将这个对象注册到内核中,通过创建一个文件 (设备节点) 绑定对象的 cdev,当我们对这个文件进行读写操作时,就可以通过虚拟文件系统,在内核中找到这个对象及其操作接口,从而控制设备。

内核笔记 424

字符设备驱动程序的三种写法

b.所有的寄存器操作着杂糅在驱动程序里,要是换另一个单板,又要重新编写整个.c文件,改进为为将具体的硬件操作和驱动程序分离开来,换另一个单板,只需提供对应的board.c文件。-c.对于每一个led硬件资源,有那么多引脚,要再抽象出一个led_resource结构体。以后要更改引脚就改资源文件board_A_led就可以了。但是对于那么多种类的硬件资源(如gpio, uart, dma, .....),用这类方法要编写一堆的资源文件,太过于麻烦。

ihs_cc的博客 286

vnpy_ctp源码下载后转变为python可用的处理过程

vnpy_ctp源码下载后在window中编译使用

m0_37967652的博客 3327

Linux字符设备驱动程序编写基本流程

系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:  1、对设备初始化和释放;  2、把数据从内核传送到硬件和从硬件读取数据;  3、读取应用程序传送给设备文

whxngb的博客 1099

从solidworks导出URDF模型

一、下载sw_urdf_exporter插件 ROS目前提供了SolidWorks转URDF的插件,叫做sw_urdf_exporter,从http://wiki.ros.org/sw_urdf_exporter下载sw2urdfSetup.exe文件,下载后点击安装,安装完成后就能在solidworks中导出URDF模型了。 二、创建solidworks模型 下面以较为简单的四轮小车为例进行说...

weixin_45168199的博客 4万+

Python实战:5分钟搞定N-CMAPSS航空发动机数据集解析(附完整代码)

本文提供了一份针对N-CMAPSS航空发动机数据集的快速解析实战指南。通过Python的h5py库,详细演示了如何读取HDF5文件结构、理解数据分类(如传感器、健康参数、场景描述符)并加载为Pandas DataFrame。文章还澄清了数据集中常见的变量数量疑惑,并给出了从数据加载到构建剩余使用寿命预测模型预处理流水线的完整代码,帮助工程师和研究人员高效入门航空发动机预测性维护分析。

weixin_29057065的博客 422

初学字符设备驱动

经过上次学习Linux的helloworld驱动,现在对驱动程序有了一点概念了。今天,接着往下学习Linux驱动咯! Linux驱动学习任务: (1)网络接口驱动:重点;(2)块设备驱动;(3)字符设备驱动:重点 Linux用户如何使用上面这些驱动程序的? 对应序号分别是: (1) 用户-->套接字-->协议栈-->网络设备驱动-->网络接口设备 (2) 用户-->文件系统-->块

2621593260的专栏 780

Windows 必看!OpenClaw 一键安装教程|告别报错,一次成功不踩坑

OpenClaw 完整安装与配置的全流程教程

m0_70389289的博客 1085

Linux驱动开发基础——Hello驱动程序(一)

file_operations 结构体 hello_drv 定义,里面提供了 open/read/write/release 成员,应用程序调用 open/read/write/close 时就会导致这些成员函数被调用。到此,学习驱动开发的开端——Hello驱动我们就完成了,希望对一起入门驱动的大佬们有帮助,觉得有帮助的可以三连!阅读一个驱动程序,从它的入口函数开始,hello_init就是入口函数。B. ./hello_drv_test -r // 把驱动中保存的字符串读回来。

2302_80169672的博客 1194

I2C 协议在 Camera Sensor 驱动中的典型配置方式与实战应用

在 Linux Camera 系统中,Sensor 与主控之间的控制信令几乎都依赖于 I2C 总线完成。I2C 协议不仅承载了 Sensor 初始化、模式切换、AE/AF 等关键功能的指令传输,也是构建 v4l2\_subdev 驱动框架的核心基础。本文将围绕 Sensor 驱动中的 I2C 通信机制展开分析,从设备树节点声明到驱动代码中的传输接口调用,结合主流平台工程实战经验,详解如何构建稳定、高效、可维护的 I2C 控制路径。

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 1713

ThingsBoard移动应用详细讲解

ThingsBoard移动应用详细讲解

AldrichEugene 527

三种Linux字符设备驱动写法-1:最简单的基本框架

学习目的:对Linux驱动整体思路有所了解,掌握最简单的Linux字符设备驱动程序写法,本文以led为例。

闪耀大叔的博客 2720

基于MATLAB的拉盖尔-高斯光束叠加相位模拟

首先,我们需要设置模拟所需的参数,如光腰半径、传播距离、光束的角动量等。其中,(U)表示光束的电场复振幅,(r)和(\phi)分别是极坐标系下的径向和角向坐标,(z)是传播距离,(A)是归一化系数,(w_p)是光束的光腰半径,(L_p^q)是拉盖尔多项式,(k)是波数,(l)是光束的角动量。通过修改模拟参数,如光腰半径、传播距离、角动量等,您可以进一步探索和理解拉盖尔-高斯光束的叠加相位效应。拉盖尔-高斯光束是一种特殊的光束模式,具有广泛的应用领域,包括光学信息处理、激光加工和量子光学等。

m0_47037246的博客 945

Linux字符设备驱动程序的编写框架

一、Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能: 1、对设备初始化和释放; 2、把数据从内核传送到硬件和从硬件

学海无涯,回头是岸........ 720

【WRF运行报错】总结WRF运行时报错及解决方案(持续更新)

总结WRF运行时报错及解决方案(持续更新)

WW、forever的博客 1630

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

字符设备驱动之Hello World

时间差不多该开始学驱动编程了, 本节开始记录学习驱动的过程。Linux设备驱动会以内核模块的形式出现,因此,在进行学习驱动编程之前,首先要有关于模块和内核编程的概念.。开发这样的专门技术对任何类型的模块化的驱动都是重要的基础.。但是,处于学习驱动编写的目的, 一个标准内核是最好的

Win_cao专栏 3674

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【Linux设备驱动】最简单的 字符设备驱动编写

了解如何编写最基础的驱动后,这一篇对最简单的字符设备驱动,做一个概述;并对代码中涉及的知识点进行一个罗列,方便大家的学习

Tschome的博客 848

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、字符设备、GPIO基础知识、LED驱动、总线设备驱动模型)

对于硬件资源,用结构体来表示;对于硬件的操作,用结构体表示。在内核中有一个虚拟的总线,它有2个链表结构,左边是设备Dev链表,右边是驱动Drv链表。左边的设备链表和右边的驱动链表会进行一一比较(通过platform_match函数),若匹配成功,就会调用中的probe函数。如何进行匹配?(2) platform_device结构体(3) platform_driver结构体总线设备驱动编写程序步骤(1) 分配、设置、注册结构体,在里面定义所用资源,指定设备名字。

DZJ_Nirvana 3124

linux中一个硬件驱动的基本过程,Linux字符设备驱动程序编写基本流程

Linux device driver 的概念系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:1、对设备初始化和释放;2、把数据从内核传送到硬件和从硬件读取数据;3、读取应用...

weixin_33561350的博客 527

LInux 驱动程序 -收藏

1.linux字符设备驱动程序--hello_world 2.

吃不饱、力不足的博客 753
上一篇: 内核模块编写
下一篇: 使用proc文件系统和内核交互
sweird
博客等级 码龄20年 336粉丝 103原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值