一、字符设备体结构介绍

1.字符设备作为linux内核三大驱动设备之一,主要完成字节的读写操作,常见的应用有鼠标、键盘等,结构体形式如下所示:

struct cdev{

struct kobject kobj;

struct module *owner;//所说模块

struct file_operations *ops;//字符设备操作方法

struct list_head list;

dev_t dev;     //设备

unsigned int count;

}

cdev结构体的dev_t成员定义了设备号,为32位,其中12位为主设备号,20位为次设备号。使用下列
宏可以从dev_t获得主设备号和次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)

而使用下列宏则可以通过主设备号和次设备号生成dev_t
MKDEV(int major, int minor)
2.字符设备结构体操作方法,Linux内核提供了一组函数以用于操作cdev结构体:
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);


cdev_init()函数用于初始化cdev的成员,并建立cdev和file_operations之间的连接
cdev_alloc()函数用于动态申请一个cdev内存
cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。

3.分配设备号
在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或
alloc_chrdev_region()函数向系统申请设备号,这两个函数的原型为:

int register_chrdev_region(dev_t from, unsigned count, const char *name); //已知起始设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);//位置起始设备号

4.file_operations结构体
file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。file_operations结构体目前已经比较庞大:
llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。它与用户空间应用程序中的ssize_t read(int fd,void*buf,size_t count)和size_t fread(void*ptr,size_t size,size_t nmemb,FILE*stream)对应。
write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。它与用户空间应用程序中的ssize_t write(int fd,constvoid*buf,size_t count)和size_t fwrite(const void*ptr,size_t size,size_t nmemb,FILE*stream)对应。
read()和write()如果返回0,则暗end-of-file(EOF)。
unlocked_ioctl()提供设备相关控制命令的实现(既不是读操作,也不是写操作),当调用成功时,返回给调用程序一个非负值。它与用户空间应用程序调用的int fcntl(int fd,int cmd,.../*arg*/)和intioctl(int d,int request,...)对应。
mmap()函数将设备内存映射到进程的虚拟地址空间中,如果设备驱动未实现此函数,用户进行mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意义,帧缓冲被映射到用户空间后,应用程序可以直接访问它而无须在内核和应用间进行内存复制。它与用户空间应用程序中的void*mmap(void*addr,size_t length,int prot,int flags,int fd,off_t offset)函数对应。

5.linux字符设备组成
(1)字符设备驱动模块加载与卸载函数
static int __init globalmem_init(void) 
static void __exit globalmem_exit(void)
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中应实现设备号的释放和cdev的注销。

二、字符结构体编码实现

在内核代码中.../drivers/   新建globalmem文件夹,在此目录下新建globalmem.c 和相应的Makefile文件

1.globalmem.c文件

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230

static int globalmem_major = GLOBALMEM_MAJOR; //定义主设备号
module_param(globalmem_major, int, S_IRUGO);//模块传参

struct globalmem_dev {   //定义globalmen_dev结构体
 struct cdev cdev;//字符结构体
 unsigned char mem[GLOBALMEM_SIZE];//使用内存
};

struct globalmem_dev *globalmem_devp;//申明globalmem结构对象

//globalmem设备驱动的读函数
static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,
 loff_t * ppos)
{
 unsigned long p = *ppos;
 unsigned int count = size;
 int ret = 0;
 struct globalmem_dev *dev = filp->private_data;

if (p >= GLOBALMEM_SIZE)
 return 0;
 if (count > GLOBALMEM_SIZE - p)
 count = GLOBALMEM_SIZE - p;
 if (copy_to_user(buf, dev->mem + p, count)) {
 ret = -EFAULT;
 } else {
 *ppos += count;
 ret = count;
 printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
 }
 return ret;
}
//globalmem设备驱动的写函数
static ssize_t globalmem_write(struct file *filp, const char __user * buf,
 size_t size, loff_t * ppos)
{
 unsigned long p = *ppos;
 unsigned int count = size;
 int ret = 0;
 struct globalmem_dev *dev = filp->private_data;

if (p >= GLOBALMEM_SIZE)
 return 0;
 if (count > GLOBALMEM_SIZE - p)
 count = GLOBALMEM_SIZE - p;

if (copy_from_user(dev->mem + p, buf, count))
 ret = -EFAULT;
 else {
 *ppos += count;
 ret = count;
 
 printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
 }
 return ret;
}
//寻址函数
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
 loff_t ret = 0;
 switch (orig) {
 case 0: /* 从文件开头位置seek */
 if (offset< 0) {
 ret = -EINVAL;
 break;
 }
 if ((unsigned int)offset > GLOBALMEM_SIZE) {
 ret = -EINVAL;
 break;
 }
 filp->f_pos = (unsigned int)offset;
 ret = filp->f_pos;
 break;
 case 1: /* 从文件当前位置开始seek */
 if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
 ret = -EINVAL;
 break;
 }
 if ((filp->f_pos + offset) < 0) {
 ret = -EINVAL;
 break;
 }
 filp->f_pos += offset;
 ret = filp->f_pos;
 break;
 default:
 ret = -EINVAL;
 break;
 }
 return ret;
}

static long globalmem_ioctl(struct file *filp, unsigned int cmd,
 unsigned long arg)
{
 struct globalmem_dev *dev = filp->private_data;
 switch (cmd) {
 case MEM_CLEAR:
 memset(dev->mem, 0, GLOBALMEM_SIZE);
 printk(KERN_INFO "globalmem is set to zero\n");
 break;
 default:
 return -EINVAL;
 }

return 0;
}
//open函数
static int globalmem_open(struct inode *inode, struct file *filp)
{
 filp->private_data = globalmem_devp;
 return 0;
}

//release函数
static int globalmem_release(struct inode *inode, struct file *filp)
{
 return 0;
}

/*定义字符结构体方法*/
static const struct file_operations globalmem_fops = {
 .owner = THIS_MODULE,
 .llseek = globalmem_llseek,
 .read = globalmem_read,
 .write = globalmem_write,
 .unlocked_ioctl = globalmem_ioctl,
 .open = globalmem_open,
 .release = globalmem_release,
};
//字符设备加载函数
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)  
{
 int err, devno = MKDEV(globalmem_major, index);//获取设备结构体dev_t

cdev_init(&dev->cdev, &globalmem_fops);//初始化字符设备和字符设备处理方法
 dev->cdev.owner = THIS_MODULE;//初始化字符设备所属模块
 err = cdev_add(&dev->cdev, devno, 1);//添加一个字符设备
 if (err)
 printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);

}

//模块初始化
static int __init globalmem_init(void) //初始化模块
{
 int ret;
 dev_t devno = MKDEV(globalmem_major, 0);//获取字符设备结构体
 if (globalmem_major)
 ret = register_chrdev_region(devno, 1, "globalmem");//注册此cdev设备
 else {
 ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");//申请字符设备cdev空间
 globalmem_major = MAJOR(devno);//获取主设备号
 }
 if (ret < 0)
 return ret;
 globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);//分配globalmem结构体内存
 if (!globalmem_devp) {
 ret = -ENOMEM;
 goto fail_malloc;    //分配失败择跳转

}

//主次设备的不同
 globalmem_setup_cdev(globalmem_devp, 0);
 return 0;
 fail_malloc:
 unregister_chrdev_region(devno, 1);
 return ret;
}
module_init(globalmem_init);

//模块卸载函数
static void __exit globalmem_exit(void)
{
 cdev_del(&globalmem_devp->cdev);
 kfree(globalmem_devp);
 unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_exit(globalmem_exit);

MODULE_AUTHOR("Barry Song <baohua@kernel.org>");

MODULE_LICENSE("GPL v2");

2.Makefile文件编写

KVERS = $(shell uname -r)
# Kernel modules
obj-m += globalmem.o

# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:

make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

3.字符设备验证

(1)在globalmem目录下make

(2)管理员身份插入模块

(3)输入字符到此字符设备中

创建设备节点:mknod /dev/globalmem c 230 0   //230 0 为你创建设备的主设备与次设备号

写入字符串:echo "hello world!">/dev/globalmem

查看输入信息:cat /dev/globalmem

查看读写情况:dmesg -c globalmem

4.编写测试代码验证

建立globalmemTest测试文件,代码如下所示:

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

int main(void)
{
char s[] = "Linux Programmer!\n";
char buffer[80];
int fd=open("/dev/globalmem",O_RDWR);//打开globalmem设备,fd返回大于2的数则成功,O_RDWR为权限赋予
write(fd,s,sizeof(s));          //将字符串s写入globalmem字符设备中
printf("test write %d %s\n",fd,s );  
        close(fd);  //关闭设备
fd=open("/dev/globalmem",O_RDWR);
read(fd,buffer,sizeof(buffer));   //读取globalmem设备中存储的数据
printf("test read %d %s\n",fd,buffer);  //输出结果显示
return 0;

}

结果展示:

linux设备驱动学习(二)——字符设备编写及测试相关推荐

  1. linux三大驱动类型:字符设备、块设备、网络设备

    1. 字符设备 字符设备指能够像字节流串行顺序依次进行访问的设备,对它的读写是以字节为单位.字符设备的上层没有磁盘文件系统,所以字符设备的file_operations成员函数就直接由字符设备驱动提供 ...

  2. linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化

    因为Linux字符设备驱动主要依赖于struct cdev结构,原型为: 所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:[unsigned int count;].[de ...

  3. Linux 设备驱动--- 阻塞型字符设备驱动 --- O_NONBLOCK --- 非阻塞标志【转】

    阅读目录 1,以阻塞方式运行: 2,以非阻塞方式运行: 转自:http://blog.csdn.net/yikai2009/article/details/8653697 版权声明:本文为博主原创文章 ...

  4. linux生成驱动编译的头文件,嵌入式Linux字符设备驱动——5生成字符设备节点

    嵌入式Linux字符设备驱动开发流程--以LED为例 前言 留空 头文件 #include 查看系统设备类 ls /sys/class 设备类结构体 文件(路径):include/linux/devi ...

  5. Linux设备驱动入门----globalmem字符设备驱动

    1.什么是globalmem虚拟设备 (1).globalmem字符设备驱动中,分配一片内存大小为GLOBALMEM_SIZE(4K)的空间 (2).提供对该片内存的读写.控制和定位函数 (3).用户 ...

  6. Linux内核驱动学习(一)编写最简单Linux内核模块HelloWorld

    文章目录 准备工作 什么是内核模块 编写 hello.c 模块编译 相关指令 测试结果 模块加载 模块卸载 准备工作 在进行以下操作前,首先我准备了一台电脑,并且安装了虚拟机,系统是Ubuntu16. ...

  7. linux v4l2系统详解,Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  8. Linux内核学习-字符设备驱动学习(二)

    在Linux内核学习-字符设备驱动学习(一)中编写字符设备驱动的一种方法,但是需要手动创建设备节点. 有没有能够自动的创建设备节点的呢? 有!使用class_create()和device_creat ...

  9. 二.字符设备驱动基础

    目录 一.开启驱动开发之路 二.最简单的模块源码分析1 2.1.常用的模块操作命令 三.最简单的模块源码分析2 3.1.模块卸载 3.2.模块中常用宏 四.最简单的模块源码分析3 4.1.printk ...

  10. Linux驱动开发:字符设备驱动开发实战

    Linux驱动开发:字符设备驱动开发实战 一.工程创建 VSCode 创建工程,设置 C/C++ 配置,导入 linux kernel 源码目录,方便 vscode 写代码自动补全,vscode 配置 ...

最新文章

  1. 塔式、机架式、刀片式服务器的区别和特点
  2. occ-backend-base-url 的相关代码,如何调试?
  3. 【BUG记录】> Android dependency ‘androidx.vectordrawable:vectordrawable‘ has different
  4. Mysql - 安装与配置
  5. elf文件格式实例解析
  6. 计算机网络与通信技术教案,计算机网络技术教案
  7. 如何测试计算机u口速度慢,如何解决电脑USB接口识别U盘速度缓慢
  8. cad卸载_CAD一键卸载工具
  9. Java 小Q 世界上最遥远的距离 解法二
  10. [Unity]Mesh Baker3.1.0使用教程
  11. 使用ffmpeg合并mp4文件
  12. 淘气的小丁-JavaScript的两种表单提交的方式
  13. 【JavaWeb】JQuery实现广告显示和隐藏动画效果
  14. 灵感1-把歌曲(mp3等)转换乐谱(简谱、五线谱)等
  15. 2017.7.27 计算机编程培训第二天
  16. APP开发技术方案模板
  17. mysql前一天的数据_mysql查询前一天数据-mysql根据时间查询前一天数据-吾爱编程网...
  18. SpringMVC(8)——格式化转换器Formatter
  19. BitConvert
  20. 普林斯顿大学颜宁教授宣布全职加盟深圳医学科学院

热门文章

  1. java mesos kubernete_Fabric8操作Kubernetes(一)
  2. gridview 在已有数据的基础上添加数据_基于Python的数据分析-1.语法基础(上)
  3. rabbitmq 启动异常_RabbitMQ:消息发送确认 与 消息接收确认(ACK)
  4. python pandas dataframe 转json_python-将嵌套的json转换为pandas dataframe
  5. centos7无法使用ifconfig且无法上网
  6. 【Python金融量化 1- 100 】了解Python及常用财经数据接口包
  7. 七十九、TodoList示例 深入Redux的工作流
  8. 物理化学 相平衡
  9. 首次开源!一行代码轻松搞定中英文语音识别、合成、翻译核心功能!
  10. 深度强化学习探索算法最新综述,近200篇文献揭示挑战和未来方向