LINUX字符设备驱动

常忆月色染枫亭 2016-08-12 11:30:33
初学这一知识点,把自己的一些心得总结一下,希望对刚接触这个概念的朋友有所借鉴~

概念性的东西暂且不提,先从一个简单的应用实例出发,来对这个知识点有个感性的认识(这里暂时不涉及硬件)

比如我们在linux用户空间如何实现与内核驱动交互?

一、首先,我们要为驱动程序申请一个设备号,这里就涉及到一个问题,什么是设备号?

每个字符设备都有一个主设备号和一个次设备号,主设备号用来标示与设备文件相连的驱动程序次设备号被驱动程序用来辨别操作的是哪一个设备,比如要控制5盏LED灯,主设备号对应某一驱动程序,次设备号指的是用该驱动程序控制那些灯亮灭。

了解到这个知识点后我们就来注册设备号,这里有两种方式实现:

方式一:调用内核提供的register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)函数,第一个参数表示主设备号(必须是可用的且系统未使用的),为0表示有系统自动分配,为了方便,这里一般将其设为零,再定义一个变量来接受这个函数的返回值得到主设备号。name表示字符设备文件的名字,第三个是一个file_operations的指针(此结构体后面会详细说明),这样一来就完成了注册。
同时,在卸载函数中则要用 int unregister_chrdev(unsigned int major, const char *name)函数去卸载。例如:

[code=cstatic

int major = 0;
static int __init chrdev_init(void)
{
major = register_chrdev(0, "mydev", const struct file_operations * fops);
return 0;
}

static void __exit chrdev_exit(void)
{
unregister_chrdev(major, "mydev");
}

][/code]


方式二:定义一个struct cdev结构表示该驱动程序,使用alloc_chrdev_region动态或者register_chrdev_region静态方式获取主设备号,然后调用void cdev_init(struct cdev *cdev, const struct file_operations *fops)和int cdev_add(struct cdev *p, dev_t dev, unsigned count)函数初始化并注册驱动

二、设备操作的实现:
详见:http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html 感谢LoveFM的分享。

三、创建设备节点
考虑到使用的普遍性,这里只介绍自动创建,使用udev机制,自动创建设备节点
class_create;
device_create;

驱动程序大致流程如上所述,可能很多朋友依旧不知所云,接下来附上代码让大家有个更直观的认识;

用方式一创建的:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linuc/fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");

static int major = 0;
static char *memdev; //用来接收用户空间发来的信息
static struct class *myclass;

//从内核读到用户空间

static ssize_t myread (struct file *filp, char __user *ubuf,
size_t size, loff_t *offset)
{
copy_to_user(ubuf, memdev, strlen(memdev));//将memdev中的数据通过ubuf传给用户空间
return strlen(memdev);
}

//从用户空间写到内核空间

static ssize_t mywrite (struct file *filp, const char __user *ubuf,
size_t size, loff_t *offset)
{
memset(memdev,0,4096);

copy_from_user(memdev, ubuf, size); //将用户空间中的数据通过ubuf传递给memdev
return size;
}

static int myopen (struct inode *inodep, struct file *filp)
{
printk("myopen have been called\n");
return 0;
}

static int myclose (struct inode *inodep, struct file *filp)
{
printk("myclose have been called\n");
return 0;
}



static struct file_operations my_fops=
{
.owner = THIS_MODULE, //在此模块运行时不能被卸载,以下四个分别调用上述对应函数
.read = myread,
.write = mywrite,
.open = myopen,
.release = myclose,

};


static int __init mychar_init(void)
{
major = register_chrdev(0, "mychar", &my_fops); //用本函数创建主设备号

//在内核中为本驱动程序开辟一段空间,并自动将这片区域清空
memdev = kzalloc(4096,GFP_KERNEL);

//调用udev自动创建设备节点,且在此目录下:sys/class/name
myclass = class_create(THIS_MODULE, "myclass");

//文件节点在此处dev/mychardev
device_create(myclass, NULL, MKDEV(major, 0), NULL, "mychrdev");
return 0;
}

static void __exit mychar_exit(void)
{
kfree(memdev);
unregister_chrdev(major, "mychar");
}


module_init(mychar_init);
module_exit(mychar_exit);



用方式二创建的:


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

MODULE_LICENSE("GPL");

static int major = 0;

static struct cdev mycdev;
static char *memsev;
static struct class *myclass;

static ssize_t myread (struct file *filp, char __user *ubuf, size_t size, loff_t *offset)
{
copy_to_user(ubuf, memdev, strlen(memdev));
return strlen(memdev);
}

static ssize_t mywrite (struct file *filp, const char __user *ubuf, size_t size, loff_t *offset)
{
memset(memdev,0,4096);

copy_from_user(memdev, ubuf, size);
return size;
}

static int myopen (struct inode *inodep, struct file *filp)
{
printk("myopen have been called\n");
return 0;
}

static int myclose (struct inode *inodep, struct file *filp)
{
printk("myclose have been called\n");
return 0;
}

static struct file_operations my_fops=
{
.owner = THIS_MODULE,
.read = myread,
.write = mywrite,
.open = myopen,
.release = myclose,

};



static int __init mycdev_init(void)
{
dev_t dev; //字符设备文件
if(major)//静态注册
{
register_chrdev_region(MKDEV(major, 0), 1, "mycdev");
}
else //动态注册
{
alloc_chrdev_region(&dev, 0, 1, "mycdev");
major = MAJOR(dev);
}

cdev_init(&mycdev, &my_fops); //初始化dev
cdev_add(&mycdev, MKDEV(major, 0), 1); //注册驱动程序,并将其添加到内核中

memdev = kzalloc(4096, GFP_ATOMIC);


myclass = class_create(THIS_MODULE, "myclass");
device_create(myclass, NULL, MKDEV(major, 0), NULL, "mycdev");

return 0;
}



static void mycdev_exit(void)
{
kfree(memdev);
cdev_del(&mycdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
}


module_init(mycdev_init);
module_exit(mycdev_exit);



最后附上用户空间测试代码:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

int main()
{
int fd;
char buf[256] = {0};


fd = open("/dev/mychrdev",O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}

write(fd,"hello,kernel!",strlen("hello,kernel!"));

read(fd,buf,256);

printf("%s\n",buf);

close(fd);

return 0;
}


希望这些能对大家有所帮助,本人第一次发帖,其中存在诸多问题,欢迎大家指正,也希望能和诸位共同探讨,共同进步!




...全文
518 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
赵4老师 2016-08-13
  • 打赏
  • 举报
回复
第一章 走进linux 1.1 GNU与Linux的成长 1.2 Linux的开发模式和运作机制 1.3走进Linux内核 1.4 分析Linux内核的意义 1.5 Linux内核结构 1.6 Linux内核源代码 1.7 Linux内核源代码分析工具 第二章 Linux运行的硬件基础 2.1 i386的寄存器 2.2 内存地址 2.3 段机制和描述符 2.4 分页机制 2.5 Linux中的分页机制 2.6 Linux中的汇编语言 第三章中断机制 3.1 中断基本知识 3.2中断描述符表的初始化 3.3异常处理 3.4 中断处理 3.5中断的后半部分处理机制 第四章 进程描述 4.1 进程和程序(Process and Program) 4.2 Linux中的进程概述 4.3 task_struct结构描述 4.4 task_struct结构在内存中的存放 4.5 进程组织的方式 4.6 内核线程 4.7 进程的权能 4.8 内核同步 第五章进程调度 5.1 Linux时间系统 5.2 时钟中断 5.3 Linux的调度程序-Schedule( ) 5.4 进程切换 第六章 Linux内存管理 6.1 Linux的内存管理概述 6.2 Linux内存管理的初始化 6.3 内存的分配和回收 6.4 地址映射机制 6.5 请页机制 6.6 交换机制 6.7 缓存和刷新机制 6.8 进程的创建和执行 第七章 进程间通信 7.1 管道 7.2 信号(signal) 7.3 System V 的IPC机制 第八章 虚拟文件系统 8.1 概述 8.2 VFS中的数据结构 8.3 高速缓存 8.4 文件系统的注册、安装与拆卸 8.5 限额机制 8.6 具体文件系统举例 8.7 文件系统的系统调用 8 .8 Linux2.4文件系统的移植问题 第九章 Ext2文件系统 9.1 基本概念 9.2 Ext2的磁盘布局和数据结构 9.3 文件的访问权限和安全 9.4 链接文件 9.5 分配策略 第十章 模块机制 10.1 概述 10.2 实现机制 10.3 模块的装入和卸载 10.4 内核版本 10.5 编写内核模块 第十一章 设备驱动程序 11.1 概述 11.2 设备驱动基础 11.3 块设备驱动程序 11.4 字符设备驱动程序 第十二章 网络 12.1 概述 12.2 网络协议 12.3 套接字(socket) 12.4 套接字缓冲区(sk_buff) 12.5 网络设备接口 第十三章 启动系统 13.1 初始化流程 13.2 初始化的任务 13.3 Linux 的Boot Loarder 13.4 进入操作系统 13.5 main.c中的初始化 13.6 建立init进程 附录: 1 Linux 2.4内核API 2.1 驱动程序的基本函数 2.2 双向循环链表的操作 2.3 基本C库函数 2.4 Linux内存管理中Slab缓冲区 2.5 Linux中的VFS 2.6 Linux的连网 2.7 网络设备支持 2.8 模块支持 2.9 硬件接口 2.10 块设备 2.11 USB 设备

33,318

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 新手乐园
社区管理员
  • 新手乐园社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧