您的位置:首页 > 其它

简单字符设备驱动流程

2015-09-10 18:36 357 查看
1.linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序:







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

2.字符设备驱动模型



3.我写的是一个简单的字符设备驱动示例,驱动操作的是一个虚拟的设备,读写的数据都在内存里。

/*********************************************************************************
*      Copyright:  (C) 2015 zq979999<zq979999@outlook.com>
*                  All rights reserved.
*
*       Filename:  mycdev.c
*    Description:  This file
*
*        Version:  1.0.0(2015年09月10日)
*         Author:  zq979999 <zq979999@outlook.com>
*      ChangeLog:  1, Release initial version on "2015年09月10日 10时39分47秒"
*
********************************************************************************/

#include <linux/module.h>   /*  模块所需的大量符号和函数定义 */
#include <linux/init.h>     /*  指定初始化和清除函数 */
#include <linux/kernel.h>   /*  printk() */
#include <linux/fs.h>       /*  struct fops */
#include <linux/errno.h>    /*  error codes */
#include <linux/cdev.h>     /*  cdev_alloc()  */
#include <asm/uaccess.h>    /*在内核和用户空间中移动数据的函数copy_to_user和copy_from_user*/
#include <linux/device.h>	/*包含了device、class 等结构的定义*/
#include <linux/slab.h>     /* kmalloc()*/

#include <asm/ioctl.h>      /*  系统空间的_IO 和 ioctl  */
#ifndef __KERNEL__
#include <sys/ioctl.h>      /*  用户空间的_IO 和 ioctl */
#endif

#define DRV_AUTHOR "zhouqing zq979999@outlook.com"
#define DEV_NAME "mycdev"

#define DRV_NAME "mycdev_driver"           	//ioctl() 函数传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值
#define MAGIC_NUM 0x97					   	//ioctl()的cmd可以通过使用宏_IO()得到
#define CDEV_HELLO _IO(MAGIC_NUM, 0x01)		//_IO (魔数, 基数);
#define CDEV_BYE _IO(MAGIC_NUM, 0x02)		//
#define SET_NUM _IO(MAGIC_NUM, 0x03)		//详细解释:
#define GET_NUM _IO(MAGIC_NUM, 0x04)		//http://blog.csdn.net/ghostyu/article/details/8085693

int i_major = 0;
int i_minor = 0;
int private_data = 0; 						//驱动运行过程中可以用来保存中间数据,本例用来保存读写的数据
static struct cdev *mycdev;					//字符设备结构体,每个字符设备对应一个结构体
static struct class *mycdev_class;			//为了实现设备节点文件自动挂载,class_create为该设备创建一个class,再为每个设备调用
//class_device_create创建对应的设备

static int mycdev_open(struct inode *inode, struct file *file)  //用户空间调用open函数,最终调用的是这里的mycdev_open
{																//下面带有read write ioctl字眼的函数同上
file->private_data = &private_data;     //每个打开的文件都会对应一个内核分配的file结构体,file结构体中有一成员为:
printk("open \n");						//void *private_data,我们可以利用该成员进行数据的传递。
return 0;
}

static ssize_t mycdev_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
int *data = filp->private_data;				//驱动里的private_data ==》file->private_data ==》 data

if(copy_to_user(buf, data, sizeof(int)))	//内核空间的数据必须通过该函数传递到用户空间
{
return -EFAULT;
}
printk("read mycdev OK\n");

return sizeof(int);

}

static ssize_t mycdev_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
int *data = filp->private_data;

if(copy_from_user(data, buf, sizeof(int)))	//用户空间的内容必须通过该函数传递到内核空间
{
return -EFAULT;
}
printk("write mycdev OK\n");

return sizeof(int);
}

static int mycdev_release(struct inode *inode, struct file *file)	//用户空间的close函数调用最终由本函数执行
{
printk("closed \n");
return 0;
}

static long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int *data = file->private_data;				//驱动里的private_data ==》file->private_data ==》 data

switch(cmd)
{
case CDEV_HELLO:printk("ioctl : CDEV_HELLO\n");
break;
case CDEV_BYE:printk("ioctl : CDEV_BYE\n");
break;
case SET_NUM:*data = (int)arg;
break;
case GET_NUM:return *data;
break;
default:printk("unkown commad\n");
}

return 0;
}

static int virtual_init(void)			//现实中,驱动操作的是硬件,一般要有一个函数进行硬件初始化,我这里没有硬件
{										//但也写了一个函数模拟这个过程
printk("initializing...\n");
return 0;
}

static struct file_operations my_fops = // file_operation结构体详解
{										//http://blog.csdn.net/l627859442/article/details/7513817
.owner = THIS_MODULE,				//就是这个结构体决定了用户空间的各种文件操作函数最终调用的实际函数
.write = mycdev_write,
.read = mycdev_read,
.open = mycdev_open,
.release = mycdev_release,
.unlocked_ioctl = mycdev_ioctl,
};

static int __init mycdev_init(void)		//整个驱动的执行起点
{
int ret;
int devno;

if( 0 != virtual_init() )      //硬件初始化,这里为模拟的
{
printk("init failure!\n");
return -ENODEV;
}

if( 0 != i_major)			//如果已经指定了主设备号
{
devno = MKDEV(i_major, 0); 	//产生设备号,MKDEV(主设备号,次设备号)。
ret = register_chrdev_region (devno, 1, DEV_NAME);   	//静态注册字符设备号
}
else
{
ret = alloc_chrdev_region(&devno, i_minor, 1, DEV_NAME); //动态注册字符设备号,保证不和现有设备号冲突
i_major = MAJOR(devno);										//由设备号计算出主设备号
}

if( ret < 0 )
{
printk("%s driver can't use major %d\n", DEV_NAME, i_major);
return -ENODEV;
}
printk("%s driver use major %d\n", DEV_NAME, i_major);

if(NULL == (mycdev = cdev_alloc()) )									//静态内存定义初始化:
{                                                                       //struct cdev my_cdev;
printk(KERN_ERR "%s driver can't alloc for the cdev.\n", DEV_NAME); //cdev_init(&my_cdev, &fops);
unregister_chrdev_region(devno, 1);                                 //my_cdev.owner = THIS_MODULE;
return -ENOMEM;                                                     //动态内存定义初始化:
}                                                                       //struct cdev *my_cdev = cdev_alloc();
//my_cdev->ops = &fops;
mycdev->owner = THIS_MODULE;                                            //my_cdev->owner = THIS_MODULE;
mycdev->ops = &my_fops;                                            		//

ret = cdev_add(mycdev, devno, 1);			//向内核添加cdev结构体
if (0 != ret)
{
printk("%s driver can't reigster cdev: result=%d\n", DEV_NAME, ret);
goto ERROR;
}

mycdev_class = class_create(THIS_MODULE, DEV_NAME);
device_create(mycdev_class, NULL, MKDEV(i_major ,0), NULL, DEV_NAME);	//在系统/dev/目录下动态创建节点

printk(KERN_ERR "%s driver[major=%d]  installed successfully!\n", DEV_NAME, i_major);

return 0;

ERROR:
printk("%s driver installed failure.\n", DEV_NAME);
cdev_del(mycdev);										//释放 cdev占用的内存
unregister_chrdev_region(devno, 1);						//解除注册
return ret;

}

static void __exit mycdev_exit(void)						//驱动卸载时执行的函数
{
dev_t devno = MKDEV(i_major, i_minor);
cdev_del(mycdev);
//kfree(mycdev);
device_destroy(mycdev_class, devno); 			//删除设备节点
class_destroy(mycdev_class);

unregister_chrdev_region(devno, 1);
printk(KERN_ERR "%s driver removed!\n", DEV_NAME);
return ;
}

module_init(mycdev_init);   //指定驱动入口函数
module_exit(mycdev_exit);	//指定驱动卸载函数
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DEV_NAME);
MODULE_LICENSE("GPL");


4.驱动测试程序

/*********************************************************************************
*      Copyright:  (C) 2015 zq979999<zq979999@outlook.com>
*                  All rights reserved.
*
*       Filename:  mycdevtst.c
*    Description:  This file
*
*        Version:  1.0.0(2015年09月10日)
*         Author:  zq979999 <zq979999@outlook.com>
*      ChangeLog:  1, Release initial version on "2015年09月10日 15时35分47秒"
*
********************************************************************************/
#include <stdio.h>
#include <linux/fcntl.h>
#include <linux/errno.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#define MAGIC_NUM 0x97
#define CDEV_HELLO _IO(MAGIC_NUM, 0x01)
#define CDEV_BYE _IO(MAGIC_NUM, 0x02)
#define SET_NUM _IO(MAGIC_NUM, 0x03)
#define GET_NUM _IO(MAGIC_NUM, 0x04)
#define UNKOWN _IO(MAGIC_NUM, 0xFF)

int main(void)
{
int fd;
int old_data;
int new_data;

if(-1 != (fd = open("/dev/mycdev", O_RDWR)))
{
if( -1 == (read(fd, &old_data, sizeof(int))))
{
printf("read error\n");
return -1;
}
else
{
printf("old data is %d\n", old_data);
}

new_data = ++old_data % 3;

if(-1 == (write(fd, &new_data, sizeof(int))))
{
printf("write error\n");
return -1;
}

if( -1 == (read(fd, &new_data, sizeof(int))))
{
printf("read error\n");
return -1;
}
else
{
printf("new data is %d\n", new_data);
}

ioctl(fd, CDEV_HELLO, 0);
ioctl(fd, CDEV_BYE, 0);
ioctl(fd, SET_NUM, 90);
new_data = ioctl(fd, GET_NUM, 0);
printf("ioctl : new data is %d\n", new_data);

ioctl(fd, UNKOWN, 0);

}
else
{
//printf("open error\n");
perror("open error");
return -1;
}
close(fd);
return 0;

}

运行结果:

zhou@zhoupc:~/lnxdrv$ sudo insmod mycdev.ko

zhou@zhoupc:~/lnxdrv$ dmesg

……

[ 7554.284097] initializing...

[ 7554.284100] mycdev driver use major 250

[ 7554.284149] mycdev driver[major=250]  installed successfully!

 

zhou@zhoupc:~/lnxdrv$ sudo ./mycdevtst

old data is 0

new data is 1

ioctl : new data is 90

 

 

zhou@zhoupc:~/lnxdrv$ dmesg

……

[ 7688.010846] open

[ 7688.010851] read mycdev OK

[ 7688.010913] write mycdev OK

[ 7688.010915] read mycdev OK

[ 7688.010923] ioctl : CDEV_HELLO

[ 7688.010924] ioctl : CDEV_BYE

[ 7688.010930] unkown commad

[ 7688.010932] closed

 

 

zhou@zhoupc:~/lnxdrv$ sudo rmmod mycdev

zhou@zhoupc:~/lnxdrv$ dmesg

[7785.892776] mycdev driver removed!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: