Linux自己编写一个字符设备驱动的实例(+代码)
Linux⾃⼰编写⼀个字符设备驱动的实例(+代码)
字符设备驱动
Linux字符设备提供连续的数据流,应⽤程序可以顺序读取,通常不⽀持随机存取。相反,此类设备⽀持按字节/字符来读写设备。举例来说,键盘,串⼝,调制解调器都是典型的字符设备。
设备分类
linux系统将设备分为3类:字符设备、块设备、⽹络设备。
字符设备:是指只能⼀个字节⼀个字节读写的设备,不能随机读取设备内存中的某⼀数据,读取数据需要按照先后数据。字符设备是⾯向流的设备,常见的字符设备有⿏标、键盘、串⼝、控制台和LED设备等。
块设备:是指可以从设备的任意位置读取⼀定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。
⽹络设备:⽹络设备⽐较特殊,不在是对⽂件进⾏操作,⽽是由专门的⽹络接⼝来实现。应⽤程序不能直接访问⽹络设备驱动程序。
在/dev⽬录下也没有⽂件来表⽰⽹络设备。
每⼀个字符设备或块设备都在/dev⽬录下对应⼀个设备⽂件。linux⽤户程序通过设备⽂件(或称设备节点)来使⽤驱动程序操作字符设备和块设备。
相关函数调⽤
struct cdev 描述字符设备的结构体
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;//⾪属于同⼀主设备号的次设备号的个数
};
cdev_alloc 动态申请(构造)cdev内存(设备对象)
居住证明范文struct cdev *cdev_alloc(void); 
struct cdev *cdev_alloc(void)
{
struct cdev *p =kzalloc(sizeof(struct cdev), GFP_KERNEL);致敬军人的经典句子10个字
//为设备申请内核内存,并对申请到的内存内容清零,GFP_KERNEL —— 正常分配内存。
if(p){
INIT_LIST_HEAD(&p->list);//初始化链表
kobject_init(&p->kobj,&ktype_cdev_dynamic);//初始化内核对象
}
return p;
}
成功的话返回值为cdev对象⾸地址
cdev_init 初始化cdev的成员,并建⽴cdev和file_operations之间关联起来
void cdev_init(struct cdev *p,const struct file_operations *p);
/* 参数:
    struct cdev *p - 被初始化的 cdev对象
    const struct file_operations *fops - 字符设备操作⽅法集 */
void cdev_init(struct cdev *cdev,const struct file_operations *fops)
{
memset(cdev,0,sizeof*cdev);
//将为cdev设置的内存空间初始化,全设为0.
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj,&ktype_cdev_default);
cdev->ops = fops;//建⽴cdev和file_operations之间的关系
}
cdev_add 注册cdev设备对象(添加到系统字符设备列表中)
int cdev_add(struct cdev *p, dev_t dev,unsigned count);
/* 参数:
    struct cdev *p - 被注册的cdev对象
    dev_t dev - 设备的第⼀个设备号
    unsigned - 这个设备连续的次设备号数量
返回值:
    成功:0
    失败:负数(绝对值是错误码)*/
int cdev_add(struct cdev *p, dev_t dev,unsigned count)
{
p->dev = dev;
p->count = count;//填充cdev结构体
return kobj_map(cdev_map, dev, count,NULL, exact_match, exact_lock, p);//添加设备号到系统字符设备列表中
}
cdev_del 将cdev对象从系统中移除(注销)
void cdev_del(struct cdev *p);
/*参数: 
    struct cdev *p - 要移除的cdev对象 */
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);//减少内核对象的引⽤计数
}
static void cdev_unmap(dev_t dev,unsigned count)
{
kobj_unmap(cdev_map, dev, count);//将设备号从系统字符设备列表中删除
}
cdev_put 释放cdev内存
void cdev_put(struct cdev *p);
/*参数:
    struct cdev *p - 要移除的cdev对象 */
void cdev_put(struct cdev *p)
{
if(p){
struct module *owner = p->owner;
kobject_put(&p->kobj);
module_put(owner);//模块卸载
}
}
设备号申请/释放
⼀个字符设备或块设备都有⼀个主设备号和⼀个次设备号。主设备号⽤来标识与设备⽂件相连的驱动程序,⽤来反映设备类型。次设备号被驱动程序⽤来辨别操作的是哪个设备,⽤来区分同类型的设备。linux内核中,设备号⽤dev_t来描述:
typedef u_long dev_t;  // 在32位机中是4个字节,⾼12位表⽰主设备号,低20位表⽰次设备号。
实现dev_t的宏
#define MAJOR(dev)((unsigned int)((dev)>> MINORBITS))
#define MINORBITS 20
//dev右移20位得到主设备号,即为⾼12位
#define MINOR(dev)((unsigned int)((dev)& MINORMASK))
#define MINORMASK ((1U << MINORBITS)- 1)
//MINOR宏将dev_t的⾼12位清零,得到次设备号。
设备号申请的⽅法
静态:
int register_chrdev_region(dev_t from,unsigned count,const char*name);
/*功能:申请使⽤从from开始的count 个设备号(主设备号不变,次设备号增加)*/
静态申请相对较简单,但是⼀旦驱动被⼴泛使⽤,这个随机选定的主设备号可能会导致设备号冲突,⽽使驱动程序⽆法注册。动态:
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char*name);
/*功能:请求内核动态分配count个设备号,且次设备号从baseminor开始。*/
动态申请简单,易于驱动推⼴,但是⽆法在安装驱动前创建设备⽂件(因为安装前还没有分配到主设备号)。
释放设备号
void unregister_chrdev_region(dev_t from,unsigned count);
编写简单的字符设备驱动
device_drive.c
# include<linux/module.h>
# include<linux/fs.h>
# include<linux/uaccess.h>
# include<linux/init.h>
# include<linux/cdev.h>
# define DEMO_NAME "my_demo_dev"
static dev_t dev;
static struct cdev *demo_cdev;
static signed count =1;
//打开操作
static int demodrv_open(struct inode *inode,struct file *file)
{
int major =MAJOR(inode->i_rdev);
int minor =MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n",__func__,major,minor);
return0;
}
/
/读操作
static ssize_t demodrv_read(struct file *file,char __user *buf,size_t lbuf,loff_t *ppos)
{
printk("%s enter\n",__func__);
return0;
return0;
}
//写操作
static ssize_t demodrv_write(struct file *file,const char __user *buf,size_t count,loff_t *f_pos)
{
printk("%s enter\n",__func__);
return0;
}
//实现设备操作
static const struct file_operations demodrv_fops ={
.owner = THIS_MODULE,
.open = demodrv_open,
.read = demodrv_read,
.write = demodrv_write
};
static int __init simple_char_init(void)
{
int ret;
ret =alloc_chrdev_region(&dev,0,count,DEMO_NAME);
/*
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);功能:请求内核动态分配count个设备号,且次设备号从baseminor开始。
*/
if(ret)
{
printk("failed to allocate char device region\n");
return ret;
}
demo_cdev =cdev_alloc();
if(!demo_cdev)
{
printk("cdev_alloc failed\n");二年级下册数学期中试卷
goto unregister_chrdev;exo-m mama
}
cdev_init(demo_cdev,&demodrv_fops);
ret =cdev_add(demo_cdev,dev,count);
if(ret)
{
printk("cdev_add failed\n");
goto cdev_fail;
}
printk("successed register char device: %s\n",DEMO_NAME);
printk("Major number = %d,minor number = %d\n",MAJOR(dev),MINOR(dev));
return0;
cdev_fail:
cdev_del(demo_cdev);
unregister_chrdev:
unregister_chrdev_region(dev,count);
return ret;
}
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
if(demo_cdev)
cdev_del(demo_cdev);
unregister_chrdev_region(dev,count);
梦见别人还钱}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_LICENSE("GPL");
Makefile
#Makefile⽂件注意:假如前⾯的.c⽂件起名为first.c,那么这⾥的Makefile⽂件中的.o⽂#件就要起名为first.o    只有root⽤户才能加载和卸载模块
obj-m:=device_drive.o                          #产⽣device_drive模块的⽬标⽂件
#⽬标⽂件⽂件要与模块名字相同
CURRENT_PATH:=$(shell pwd)            #模块所在的当前路径
LINUX_KERNEL:=$(shell uname -r)        #linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules    #编译模块#[Tab]内核的路径当前⽬录编译完放哪表明编译的是内核模块
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean      #清理模块
test.c
# include<stdio.h>
# include<fcntl.h>
# include<unistd.h>
# define DEMO_DEV_NAME "/dev/demo_drv"
int main()
{
char buffer[64];
int fd;
fd =open(DEMO_DEV_NAME,O_RDONLY);
if(fd<0)
电脑设置密码
{
printf("open device %s failed\n",DEMO_DEV_NAME);
return-1;
}
read(fd,buffer,64);
close(fd);
return0;
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。