一、字符设备基础

字符设备:是指只能一个字节一个字节进行读写操作的设备,不能随机读取设备中的某一数据、读取数据要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED等。

一般每个字符设备或者块设备都会在/dev目录(可以是任意目录,这样是为了统一)下对应一个设备文件。linux用户层程序通过设备文件来使用驱动程序操作字符设备或块设备。

二、字符设备驱动与用户空间访问该设备的程序三者之间的关系

字符设备是3大类设备(字符设备、块设备、网络设备)中较简单的一类设备、其驱动程序中完成的主要工作是初始化、添加和删除 struct cdev 结构体,申请和释放设备号,以及填充 struct file_operations 结构体中断的操作函数,实现 struct file_operations 结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。

如图,在Linux内核代码中:

  • 使用struct cdev结构体来抽象一个字符设备;
  • 通过一个dev_t类型的设备号(分为主(major)、次设备号(minor))一确定字符设备唯一性;
  • 通过struct file_operations类型的操作方法集来定义字符设备提供个VFS的接口函数。

 三、字符设备模型

1、Linux内核中,使用 struct cdev 来描述一个字符设备

<include/linux/cdev.h>  

struct cdev {
  struct kobject kobj; //内嵌的内核对象.
  struct module *owner; //该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULE主要用于模块计数
  const struct file_operations *ops; //该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、...),是极为关键的一个结构体
  struct list_head list; //用来将已经向内核注册的所有字符设备形成链表
  dev_t dev; //字符设备的设备号,由主设备号和次设备号构成(如果是一次申请多个设备号,此设备号为第一个)
  unsigned int count; //隶属于同一主设备号的次设备号的个数
  ...
};

对于struct cdev内核提供了一些操作接口:

 头文件linux/cdev.h

动态申请(构造)cdev内存(设备对象)

struct cdev *cdev_alloc(void);  
/* 返回值:
    成功 cdev 对象首地址
    失败:NULL */

初始化cdev的成员,并建立cdev和file_operations之间关联起来 

void cdev_init(struct cdev *p, const struct file_operations *p);  
/* 参数:
    struct cdev *p - 被初始化的 cdev对象
    const struct file_operations *fops - 字符设备操作方法集 */

注册cdev设备对象(添加到系统字符设备列表中)

int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/* 参数:
    struct cdev *p - 被注册的cdev对象
    dev_t dev - 设备的第一个设备号
    unsigned - 这个设备连续的次设备号数量
返回值:
    成功:0
    失败:负数(绝对值是错误码)*/

将cdev对象从系统中移除(注销 )

void cdev_del(struct cdev *p);
/*参数: 
    struct cdev *p - 要移除的cdev对象 */

释放cdev内存

void cdev_put(struct cdev *p);
/*参数:
    struct cdev *p - 要移除的cdev对象 */

2、设备号申请/释放

一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。

linux内核中,设备号用dev_t来描述:

typedef u_long dev_t;  // 在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。

内核也为我们提供了几个方便操作的宏实现dev_t:

#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))  // 从设备号中提取主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))  // 从设备号中提取次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))</span>  // 将主、次设备号拼凑为设备号
/* 只是拼凑设备号,并未注册到系统中,若要使用需要竞态申请 */

头文件 linux/fs.h 

a - 静态申请设备号

int register_chrdev_region(dev_t from, unsigned count, const char *name);
/* 参数:
    dev_t from - 要申请的设备号(起始)
    unsigned count - 要申请的设备号数量
    const char *name - 设备名
返回值:
    成功:0
    失败:负数(绝对值是错误码)*/

b - 动态分配设备号

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/* 参数:
    dev_t *dev - 用于保存分配到的第一个设备号(起始)
    unsigned baseminor - 起始次设备号
    unsigned count - 要分配设备号的数量
    const char *name - 设备名
返回值:
    成功:0
    失败:负数(绝对值是错误码)*/

c - 释放设备号

void unregister_chrdev_region(dev_t from, unsigned count);
/* 参数:
    dev_t from - 要释放的第一个设备号(起始)
    unsigned count - 要释放的次设备号数量 */

d、创建设备文件:

利用cat /proc/devices查看申请到的设备名,设备号。

  1. 使用mknod手工创建:mknod filename type major minor
  2. 自动创建设备节点:利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。

详细解析见: Linux设备文件自动生成

3、struct cdev 中的 file_operations *fops成员

Linux下一切皆是“文件”,字符设备也是这样,file_operations结构体中的成员函数是字符设备程序设计的主题内容,这些函数实际会在用户层程序进行Linux的open()、close()、write()、read()等系统调用时最终被调用。

标准化:如果做到极致,应用层仅仅需要一套系统调用接口函数。

"文件"的操作接口结构:

struct file_operations {
  struct module *owner;  
    /* 模块拥有者,一般为 THIS——MODULE */
  ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  
    /* 从设备中读取数据,成功时返回读取的字节数,出错返回负值(绝对值是错误码) */
  ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);   
    /* 向设备发送数据,成功时该函数返回写入字节数。若为被实现,用户调层用write()时系统将返回 -EINVAL*/
  int (*mmap) (struct file *, struct vm_area_struct *);  
    /* 将设备内存映射内核空间进程内存中,若未实现,用户层调用 mmap()系统将返回 -ENODEV */
  long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg);  
    /* 提供设备相关控制命令(读写设备参数、状态,控制设备进行读写...)的实现,当调用成功时返回一个非负值 */
  int (*open) (struct inode *, struct file *);  
    /* 打开设备 */
  int (*release) (struct inode *, struct file *);  
    /* 关闭设备 */
  int (*flush) (struct file *, fl_owner_t id);  
    /* 刷新设备 */
  loff_t (*llseek) (struct file *, loff_t, int);  
    /* 用来修改文件读写位置,并将新位置返回,出错时返回一个负值 */
  int (*fasync) (int, struct file *, int);  
    /* 通知设备 FASYNC 标志发生变化 */
  unsigned int (*poll) (struct file *, struct poll_table_struct *);  
    /* POLL机制,用于询问设备是否可以被非阻塞地立即读写。当询问的条件未被触发时,用户空间进行select()和poll()系统调用将引起进程阻塞 */
  ...
};

四、简单字符设备实例

cdev_module.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <asm/current.h>
#include <linux/sched.h>
MODULE_LICENSE("GPL");
static int major = ; 
static int minor = ;
const int count = ;
#define DEVNAME "demo"
static struct cdev *demop = NULL;
//打开设备
static int demo_open(struct inode *inode, struct file *filp)
{
  //get command and pid
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
  //get major and minor from inode
  printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
  return ;
}
//关闭设备
static int demo_release(struct inode *inode, struct file *filp)
{
  //get command and pid
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
  //get major and minor from inode
  printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
  return ;
}
//读设备
static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
  struct inode *inode = filp->f_path.dentry->d_inode;
  //get command and pid
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
  //get major and minor from inode
  printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
  return ;
}
//写设备
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
  struct inode *inode = filp->f_path.dentry->d_inode;
  //get command and pid
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
  //get major and minor from inode
  printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
  return ;
}
//操作方法集
static struct file_operations fops = {
  .owner = THIS_MODULE, .open = demo_open,
  .release= demo_release,
  .read = demo_read,
  .write = demo_write,
};
//cdev设备模块初始化
static int __init demo_init(void)
{
  dev_t devnum; int ret;
  //get command and pid
  printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
  //1. alloc cdev obj
  demop = cdev_alloc();
  if(NULL == demop) {
    return -ENOMEM;
  }
//2. init cdev obj
cdev_init(demop, &fops);
ret = alloc_chrdev_region(&devnum, minor, count, DEVNAME);
if(ret){
goto ERR_STEP;
}
major = MAJOR(devnum);
//3. register cdev obj
ret = cdev_add(demop, devnum, count);
if(ret){
goto ERR_STEP1;
}
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
return ; ERR_STEP1:
unregister_chrdev_region(devnum, count); ERR_STEP:
cdev_del(demop);
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - fail.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
return ret;
} static void __exit demo_exit(void)
{
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
unregister_chrdev_region(MKDEV(major, minor), count);
cdev_del(demop);
} module_init(demo_init);
module_exit(demo_exit);

test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> int main(int num, char *arg[])
{
if( != num){
printf("Usage: %s /dev/devfile\n", arg[]);
return -;
}
int fd = open(arg[], O_RDWR);
if( > fd){
perror("open");
return -;
}
getchar();
int ret = read(fd, 0x321, );
printf("read: ret = %d.\n", ret);
getchar();
ret = write(fd, 0x123, );
printf("write: ret = %d.\n", ret);
getchar();
close(fd);
return ;
}

Makefile

ifneq ($(KERNELRELEASE),)
obj-m = demo.o
else
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif clean:
rm -rf .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c

编译成功后,使用 insmod 命令加载:

然后用cat /proc/devices 查看,会发现设备号已经申请成功;

Linux字符设备驱动的更多相关文章

  1. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  2. Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...

  3. Smart210学习记录----beep linux字符设备驱动

    今天搞定了beep linux字符设备驱动,心里还是很开心的,哈哈...但在完成的过程中却遇到了一个非常棘手的问题,花费了我大量的时间,,,, 还是把问题描述一下吧,好像这个问题很普遍的,网上许多解决 ...

  4. Linux字符设备驱动实现

    Linux字符设备驱动实现 要求 编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序.可以有一个读,一个写进程共享该字符设备,进行聊天:也可以由多个读和多个写进程共享该字符设备,进 ...

  5. Linux字符设备驱动基本结构

    1.Linux字符设备驱动的基本结构 Linux系统下具有三种设备,分别是字符设备.块设备和网络设备,Linux下的字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中某一数据,读取数据 ...

  6. (57)Linux驱动开发之三Linux字符设备驱动

    1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...

  7. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  8. Linux 字符设备驱动模型

    一.使用字符设备驱动程序 1. 编译/安装驱动 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码.因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块 2. 创建设备文件 通 ...

  9. linux字符设备驱动学习笔记(一):简单的字符设备驱动

    最近在鼓捣lnux字符设备驱动,在网上搜集的各种关于linux设备驱动的代码和注释,要么是针对2.4的,要么是错误百出,根本就不能运行成功,真希望大家在发博客的时候能认真核对下代码的正确性,特别是要把 ...

随机推荐

  1. 返回到上一页的html代码的几种写法

    关键词:返回上一页 html代码超链接返回上一页代码: <a href=”#” onClick=”javascript :history.back(-1);”>返回上一页</a> ...

  2. 如何分析apache日志[access_log(访问日志)和error_log(错误日志)]

    如何分析apache日志[access_log(访问日志)和error_log(错误日志)] 发布时间: 2013-12-17 浏览次数:205 分类: 服务器 默认Apache运行会access_l ...

  3. SQL从入门到基础 - 01 数据库开发及ADO.Net

    一.数据库概述 1. 用自定义文件格式保存数据的劣势:并发性差,查找数据的速度差. 2. DBMS(DataBase Management System数据库管理系统)和数据库.平时谈到“数据库”的含 ...

  4. 网页上facebook分享功能的具体实现

    1,一个链接: 参数是要分享的页面的链接 代码如下: <a style="width:35px; height:40px; position:relative; top:10px; l ...

  5. codeforces 672 D

    题目链接:http://codeforces.com/problemset/problem/672/D 题目大意:进行k次操作,每次将最大值集合中最大值-1,最小值+1,问你K次操作之后,最大值和最小 ...

  6. JSTL与EL之间的千丝万缕

    一.关于JSTL和EL: 什么是JSTL? JSTL( JSP Standard Tag Library)是JSP标准 标签库,由apache实现. 什么是EL? EL(Expression Lang ...

  7. FlowPlayer 参数说明

    <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> & ...

  8. addChildViewController 用法

    // // SCMyOrderViewController.m // SmartCommunity // // Created by chenhuan on 15/9/7. // Copyright ...

  9. c# windows 服务学习

    用C#做windows服务变得简单对了===按照下面步骤来就行了 用C#创建Windows服务(Windows Services)例子服务功能:这个服务在启动和停止时,向一个文本文件中写入一些文字信息 ...

  10. Core开发-后台任务利器Hangfire使用

    Core开发-后台任务利器Hangfire使用 ASP.NET Core开发系列之后台任务利器Hangfire 使用. Hangfire 是一款强大的.NET开源后台任务利器,无需Windows服务/ ...