linux 驱动编写之虚拟字符设备的编写实例详解

 linux 驱动编写

前言:

昨天我们说了一些简单模块编写方法,但是终归没有涉及到设备的编写内容,今天我们就可以了解一下相关方面的内容,并且用一个实例来说明在Linux上面设备是如何编写的。虽然我不是专门做linux驱动的,却也经常收到一些朋友们的来信。在信件中,很多做驱动的朋友对自己的工作不是很满意,认为自己的工作就是把代码拷贝来拷贝去,或者说是改来改去,没有什么技术含量。有这种想法的朋友不在少数,我想这主要还是因为他们对自己的工作缺少了解导致。如果有可能,我们可以问问自己这样几个问题:

(1)我真的搞懂设备的开发驱动流程了吗?我是否可以从0开始,编写一个独立的驱动代码呢?
    (2)我真的了解设备的初始化、关闭、运行的流程吗?
    (3)当前的设备驱动流程是否合理,有没有可以改进的地方?
    (4)对于内核开发中涉及的api调用,我自己是否真正了解、是否明白它们在使用上有什么区别?
    (5)如果我要驱动的设备只是在一个前后台系统中运行,在没有框架帮助的情况下,我是否有信心把它启动和运行起来?

当然,上面的内容只是我个人的想法,也不一定都正确。但是,知其然,更要知其所以然,熟悉了当前开发流程的优缺点才能真正掌握和了解驱动开发的本质。这听上去有些玄乎,其实也很简单,就是要有一种刨根问底、不断改进的精神,这样才能做好自己的工作。因为我们是在pc linux上学习驱动的,因此暂时没有真实的外接设备可以使用,但是这丝毫不影响我们学习的热情。通过定时器、进程,我们可以仿真出真实设备的各种需求,所以对于系统来说,它是无所谓真设备、假设备的,基本的处理流程对它来说都是一样的。只要大家一步一步做下去,肯定可以了解linux驱动设备的开发工程的。

下面,为了说明问题,我们可以编写一段简单的char设备驱动代码,文件名为char.c,

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h> 

static struct cdev chr_dev;
static dev_t ndev; 

static int chr_open(struct inode* nd, struct file* filp)
{
  int major ;
  int minor; 

  major = MAJOR(nd->i_rdev);
  minor = MINOR(nd->i_rdev); 

  printk("chr_open, major = %d, minor = %d\n", major, minor);
  return 0;
} 

static ssize_t chr_read(struct file* filp, char __user* u, size_t sz, loff_t* off)
{
  printk("chr_read process!\n");
  return 0;
} 

struct file_operations chr_ops = {
  .owner = THIS_MODULE,
  .open = chr_open,
  .read = chr_read
}; 

static int demo_init(void)
{
  int ret; 

  cdev_init(&chr_dev, &chr_ops);
  ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev");
  if(ret < 0 )
  {
    return ret;
  } 

  printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
  ret = cdev_add(&chr_dev, ndev, 1);
  if(ret < 0)
  {
    return ret;
  } 

  return 0;
} 

static void demo_exit(void)
{
  printk("demo_exit process!\n");
  cdev_del(&chr_dev);
  unregister_chrdev_region(ndev, 1);
} 

module_init(demo_init);
module_exit(demo_exit); 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("feixiaoxing@163.com");
MODULE_DESCRIPTION("A simple device example!");

在module_init中的函数是模块加载时处理的函数,而模块卸载的函数则是在module_exit中。每一个设备都要对应一个基本的设备数据,当然为了使得这个设备注册在整个系统当中,我们还需要分配一个设备节点,alloc_chrdev_region就完成这样一个功能。等到cdev_add的时候,整个设备注册的过程就全部完成了,就是这么简单。当然为了编写这个文件,我们还需要编写一个Makefile文件,

ifneq ($(KERNELRELEASE),)
obj-m := char.o 

else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
  $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
  rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif

这个Makefile文件和我们之前编写的makefile基本上没有区别,唯一的区别就是文件名称改成了char.o,仅此而已。为了编写模块,我们直接输入make即可。这时候,char.ko文件就可以生成了。然后,模块需要被注册在系统当中,insmod char.ko是少不了的。如果此时,我们还不确信是否模块已经加入到系统当中,完全可以通过输入lsmod | grep char进行查找和验证。为了创建设备节点,我们需要知道设备为我们创建的major、minor数值是多少,所以dmesg | tail 查找一下数值。在我hp的机器上,这两个数值分别是249和0,所以下面可以利用它们直接创建设备节点了,输入mknod /dev/chr_dev c 249 0即可,此时可以输入ls /dev/chr_dev验证一下。那么,按照这种方法,真的可以访问这个虚拟设备了吗,我们可以编写一段简单的代码验证一下,

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h> 

#define CHAR_DEV_NAME "/dev/chr_dev" 

int main()
{
  int ret;
  int fd;
  char buf[32]; 

  fd = open(CHAR_DEV_NAME, O_RDONLY | O_NDELAY);
  if(fd < 0)
  {
    printf("open failed!\n");
    return -1;
  } 

  read(fd, buf, 32);
  close(fd); 

  return 0;
}

代码的内容非常简单,就是利用CHAR_DEV_NAME直接打开设备,读写设备。当然。首先还是需要对这个文件进行编译,文件名为test.c,输入gcc test.c -o test,其次就是运行这个文件,直接输入./test即可。如果没有问题的话,那么说明我们的代码是ok的,但是我们还是没有看到任何内容。没关系,我们还是通过dmesg这个命令查看内核中是否存在相关的打印内容,直接输入dmesg | tail即可。此时如果没有意外的话,我们就可以看到之前在chr_open和chr_read中留下的printk打印,这说明我们的代码完全是ok的。

上面的代码只是一段小例子,真实的内容要比这复杂一下。不过既然我们都已经入门了,那么后面的内容其实也没有什么好怕的了。最后有两个事情补充一下:(1)如果大家在创建节点后想删除设备节点,直接rm -rf /dev/chr_dev即可;(2)上面这段代码的原型来自于《深入linux设备驱动程序内核机制》这本书,稍作修改,如果大家对内核机制的内容感兴趣,可以参考这本书的内容。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • 详解 linux c++的编译器g++的基本使用

    linux c++的编译器g++基本使用 g++是 linux下c++的编译器,在执行编译工作的时候,总共需要4步 1.预处理,生成.i的文件 2.将预处理后的文件不转换成汇编语言,生成文件.s 3.有汇编变为目标代码(机器代码)生成.o的文件 4.连接目标代码,生成可执行程序 g++ 编译c++经常使用的参数: -c 只编译,不连接.例如: g++ -c helloworld.cpp 只生成helloworld.o不连接 -o 指定输出文件名.例如:g++ -c helloworld.cpp

  • linux 驱动之Kconfig文件和Makefile文件实例

    linux 驱动之Kconfig文件和Makefile文件实例 在Linux编写驱动的过程中,有两个文件是我们必须要了解和知晓的.这其中,一个是Kconfig文件,另外一个是Makefile文件.如果大家比较熟悉的话,那么肯定对内核编译需要的.config文件不陌生,在.config文件中,我们发现有的模块被编译进了内核,有的只是生成了一个module.这中间,我们如何让内核发现我们编写的模块呢,这就需要在Kconfig中进行说明.至于如何生成模块,那么就需要利用Makefile告诉编译器,怎么

  • Linux忘记root密码怎么办

    介绍个人使用的三个系统的修改方式.包括centos6.6,centos7和ubuntu15.04/linuxmint17.2.大家可以通过本文学习下. CentOS6.6 重启,进入Grub时,上下方向键选择CentOS6,按e,选择kernel那一项. 按e,在后面输入single,回车,按b启动. 使用命令passwd root修改root密码,重启. CentOS7 重启,进入Grub时,上下方向键选择第一项,按e,进入编辑. 在倒数第二行最后,输入rd.break,使用快捷键Ctrl+x

  • linux C语言开发管道通信实例详解

    linux C语言开发管道通信 Linux系统本身为进程间通信提供了很多的方式,比如说管道.共享内存.socket通信等.管道的使用十分简单,在创建了匿名管道之后,我们只需要从一个管道发送数据,再从另外一个管道接受数据即可. #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> int pipe_default[2]; int main() { pid_t

  • 详解Linux--shell脚本之正则表达式

    一.正则表达式的概念及特点: 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个"规则字符串",这个"规则字符串"用来表达对字符串的一种过滤逻辑.规定一些特殊语法表示字符类.数量限定符和位置关系,然后用这些特殊语法和普通字符一起表示一个模式,这就是正则表达式(Regular Expression). 给定一个正则表达式和另一个字符串,我们可以达到如下的目的: 1. 给定的字符串是否符合正则表达式的过滤逻辑(称作&q

  • Linux 配置静态IP的方法

    在新安装的Linux系统命令行下, 敲入:ifconfig,显示如下界面. 上面这张图显示网卡没有启动,那么我们敲入代码:ifup eth0启动网卡. 网卡启动后,我们可以看出,IP地址和网关等其他信息都已经出现. 但是我们需要的是静态IP,即不随着时间改变而改变的IP地址. 首先我们要知道我们的网关地址是多少. 敲入代码:route ,下图中的192.168.164.2就是我们的默认网管地址,记住这个,下面的配置需要用这个. 继续敲入代码: vi /etc/sysconfig/network-

  • linux下mysql的root密码忘记的解决方法

    五步轻松解决mysql root密码忘记的问题,希望对大家有帮助. 1.修改MySQL的登录设置: # vi /etc/my.cnf 在[mysqld]的段中加上一句,skip-grant-tables 例如: [mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock skip-grant-tables 2.重新启动mysql # service mysql start 3.登录并修改MySQL的root密码 # mysql

  • Linux下使用docker搭建Openvpn代理的方法

    说明 openvpn方式与pptpd方式对比(个人感觉): 复杂度:openvpn>pptpd 安全性:openvpn>pptpd 稳定性:openvpn>pptpd 1.安装 1.下载 docker pull kylemanna/openvpn 2.全局变量(方便设置) OVPN_DATA="/root/ovpn-data" // 下面的全局变量换成你的服务器的外网ip IP="xxx.xxx.xxx.xxx" 3.创建文件目录 mkdir ${

  • linux 驱动编写之虚拟字符设备的编写实例详解

     linux 驱动编写 前言: 昨天我们说了一些简单模块编写方法,但是终归没有涉及到设备的编写内容,今天我们就可以了解一下相关方面的内容,并且用一个实例来说明在Linux上面设备是如何编写的.虽然我不是专门做linux驱动的,却也经常收到一些朋友们的来信.在信件中,很多做驱动的朋友对自己的工作不是很满意,认为自己的工作就是把代码拷贝来拷贝去,或者说是改来改去,没有什么技术含量.有这种想法的朋友不在少数,我想这主要还是因为他们对自己的工作缺少了解导致.如果有可能,我们可以问问自己这样几个问题: (

  • linux驱动开发中常用函数copy_from_user open read write详解

    目录 linux驱动常用函数(copy_from_user open read write) 1.open 2.read 3.write 4.copy_to_user 5.copy_from_user linux驱动常用函数(copy_from_user open read write) 1.open 函数定义: int open( const char * pathname, int flags); int open( const char * pathname,int flags, mode

  • 微信小程序 获取设备信息 API实例详解

    获取设备信息这里分为四种, 主要属性: 网络信息wx.getNetWorkType, 系统信息wx.getSystemInfo, 重力感应数据wx.onAccelerometerChange, 罗盘数据wx.onCompassChange wxml <button type="primary" bindtap="getNetWorkType">获取网络类型</button> <button type="primary"

  • Linux上的文件搜索命令实例详解

    locate 基础了解 在centos7上默认没有locate命令,需要先手动安装.安装步骤:http://www.cnblogs.com/feanmy/p/7676717.html locate命令搜索的后台数据库路径:/var/lib/mlocate/mlocate.db ls -hl /var/lib/mlocate total 1.2M -rw-r----- 1 root slocate 1.2M Oct 16 14:36 mlocate.db 更新数据库使用updatedb,配置文件为

  • Linux 6 下编译安装 PHP 5.6实例详解

    Linux 6 下编译安装 PHP 5.6实例详解 PHP(外文名:PHP: Hypertext Preprocessor,中文名:"超文本预处理器")是一种通用开源脚本语言.语法吸收了C语言.Java和Perl的特点,利于学习,使用广泛,主要适用于Web开发领域.PHP以其开发源代码,免费,快捷,跨平台,高效,面向对象,强大的动态图像创建等功能深受广大开发者的喜爱.本文描述基于CentOS 6.7下编译安装PHP 5.6.9,同样也适用于CentOS 7下安装. 一.相关依赖包安装

  • linux更改目录显示颜色实例详解

    linux更改目录显示颜色实例详解 用shell列举目录的时候,文件夹都是蓝色的,背景是黑色,使得无法看清蓝色的文件名称,看起来很痛苦.这个已经好几次遇到这个问题了都没有把解决方法记录下来,导致每次要查一些资料,这次决定把这个方法整理下来,供以后遇到同样的情况之后使用. 针对文件的解决方式 为当前用户配置,在当前用户home目录下的./bashrc中添加下面的参数即可. 在这里简单修改了文件夹的格式为粗体,前景色是黄色,背景色是黑色.还有引用为粗体,青色前景色,黑色背景色. 这里着重调一下di相

  • Linux 全能系统监控工具dstat的实例详解

    全能系统监控工具dstat dstat 是一个可以取代vmstat,iostat,netstat和ifstat这些命令的多功能产品.dstat克服了这些命令的局限并增加了一些另外的功能,增加了监控项,也变得更灵活了.dstat可以很方便监控系统运行状况并用于基准测试和排除故障. dstat可以让你实时地看到所有系统资源,例如,你能够通过统计IDE控制器当前状态来比较磁盘利用率,或者直接通过网络带宽数值来比较磁盘的吞吐率(在相同的时间间隔内). dstat将以列表的形式为你提供选项信息并清晰地告诉

  • Linux静态库与动态库实例详解

    Linux静态库与动态库实例详解 1. Linux 下静态链接库编译与使用 首先编写如下代码: // main.c #include "test.h" int main(){ test(); return 0; } // test.h #include<iostream> using namespace std; void test(); // test.c #include "test.h" void test(){ cout<< &quo

  • Linux内存描述符mm_struct实例详解

    Linux对于内存的管理涉及到非常多的方面,这篇文章首先从对进程虚拟地址空间的管理说起.(所依据的代码是2.6.32.60) 无论是内核线程还是用户进程,对于内核来说,无非都是task_struct这个数据结构的一个实例而已,task_struct被称为进程描述符(process descriptor),因为它记录了这个进程所有的context.其中有一个被称为'内存描述符'(memory descriptor)的数据结构mm_struct,抽象并描述了Linux视角下管理进程地址空间的所有信息

  • Linux 在Shell脚本中使用函数实例详解

    Linux 在Shell脚本中使用函数实例详解 Shell的函数 Shell程序也支持函数.函数能完成一特定的功能,可以重复调用这个函数. 函数格式如下: 函数名() { 函数体 } 函数调用方式: 函数名 参数列表 实例:编写一函数add求两个数的和,这两个数用位置参数传入,最后输出结果. root@ubuntu:/home/study# vi test3 #!/bin/bash add(){ a=$1; b=$2; z=`expr $a + $b`; echo "The sum is $z&

随机推荐