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;
}


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




...全文
492 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
赵4老师 2016-08-13
  • 打赏
  • 举报
回复

33,311

社区成员

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

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