具体理论分析字符设备
1、通用字符设备驱动摸版
//file open operation function
static int char_drv_open(struct inode *inode , struct file *filp)
{
return 0;
}
//file read operation function
static int char_drv_read(struct file *filp , char __user *buf , size_t cnt , loff_t *offt)
{
return 0;
}
//file write operation function
static int char_drv_write(struct file *filp,const char __user *buf , size_t cnt , loff_t *offt)
{
return 0;
}
//file close operation function
static int char_drv_release(struct inode *inode , struct file *filp)
{
return 0;
}
//file operation function struct
static struct file_openration my_test_fop=
{
.owner = THIS_MODULE,
.open = char_drv_open,
.read = char_drv_read,
.write = char_drv_write,
.release = char_drv_release
};
struct cdev test_dev;
struct class *class;
struct devic *device;
dev_t devid;
/* 设备结构体 */
struct test_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
#define NEWCHRLED_CNT 1 /* 设备号个数 */
#define NEWCHRLED_NAME "newchrdev" /* 名字 */
struct test_dev test_char_dev;
//module init function
static int __init char_drv_test_init(void)
{
//same hardware init
//apply device num
alloc_chrdev_region(&test_char_dev.devid, 0, NEWCHRLED_CNT,NEWCHRLED_NAME);
test_char_dev.major = MAJOR(test_char_dev.devid); /* 获取主设备号 */
test_char_dev.minor = MINOR(test_char_dev.devid); /* 获取次设备号 */
//init dev struct
cdev_init(&test_char_dev.cdev,&my_test_fop);
//add dev to system
cdev_add(&test_char_dev.cdev ,test_char_dev.devid ,NEWCHRLED_CNT );
//build class
test_char_dev.class = class_create(THIS_MODULE,"test");
if (IS_ERR(newchrled.class))
{
return PTR_ERR(newchrled.class);
}
//build device
test_char_dev.device = device_create(test_char_dev.class,NULL ,devid,NULL,"test");
if (IS_ERR(newchrled.device))
{
return PTR_ERR(newchrled.device);
}
return 0;
}
//module uninstall function
static void __exit char_drv_test_exit(void)
{
/* 注销字符设备 */
cdev_del(&newchrled.cdev);/* 删除 cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}
//module function band
module_init(char_drv_test_init);
module_exit(char_drv_test_exit);
//license and author
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Smile");
2、例1
2.1、目标
编写简单驱动程序,并加载到内核中,等待被用户程序调用
2.2、驱动函数
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
static dev_t devno;
#define KMAX_LEN 32
//open
static int demo_open(struct inode *ind, struct file *fp)
{
printk("demo open\n");
return 0;
}
//close
static int demo_release(struct inode *ind, struct file *fp)
{
printk("demo release\n");
return 0;
}
//read
static ssize_t demo_read(struct file *fp, char __user *buf, size_t size, loff_t *pos)
{
int rc;
char kbuf[KMAX_LEN] = "read test\n";
if (size > KMAX_LEN)
size = KMAX_LEN;
rc = copy_to_user(buf, kbuf, size);
if(rc < 0) {
return -EFAULT;
pr_err("copy_to_user failed!");
}
return size;
}
//write
static ssize_t demo_write(struct file *fp, const char __user *buf, size_t size, loff_t *pos)
{
int rc;
char kbuf[KMAX_LEN];
if (size > KMAX_LEN)
size = KMAX_LEN;
rc = copy_from_user(kbuf, buf, size);
if(rc < 0) {
return -EFAULT;
pr_err("copy_from_user failed!");
}
printk("%s\n", kbuf);
return size;
}
//初始化我们需要的文件接口结构体中的内容
static struct file_operations fops = {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
};
//定义一个字符设备
static struct cdev cd;
//初始化函数
static int demo_init(void)
{
int rc;
//1、静态分配设备号
/*内核源码:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}*/
//动态分配设备号
/*内核源码:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}*/
rc = alloc_chrdev_region(&devno, 0, 1, "test");
if(rc < 0) {
pr_err("alloc_chrdev_region failed!");
return rc;
}
printk("MAJOR is %d\n", MAJOR(devno));//可以通过dmesg查看日志信息,获得驱动输出的相关信息
printk("MINOR is %d\n", MINOR(devno));
//2、初始化字符设备
/*内核源码:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
*/
cdev_init(&cd, &fops);
//3、字符设备加入到系统
/*内核源码:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
*/
rc = cdev_add(&cd, devno, 1);
if (rc < 0) {
pr_err("cdev_add failed!");
return rc;
}
return 0;
}
//卸载函数
static void demo_exit(void)
{
//将字符设备从系统中删除
/*内核源码
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p-> kobj);
}
*/
cdev_del(&cd);
unregister_chrdev_region(devno, 1);
return;
}
//驱动注册
module_init(demo_init);
//驱动卸载
module_exit(demo_exit);
//必要的声明
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Smile");
2.3、应用函数
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int rc;
char buf[32];
int fd = open("/dev/test", O_RDWR);
if (fd < 0) {
printf("open file failed!\n");
return -1;
}
read(fd, buf, 32);
printf("%s", buf);
write(fd, "write test\n", 32);
close(fd);
return 0;
}
2.4、通用Makefile
ifneq ($(KERNELRELEASE),)
obj-m := cdev_driver.o #具体修改只需要该.o之前的变量名和内核源码目录路径
else
KERNEL_DIR := /lib/modules/$(shell uname -r)/build # 取内核的build目录给KDIR
PWD := $(shell pwd) # 表示取当前的pwd值赋给PWD变量
all:
make -C $(KERNEL_DIR) M=$(PWD)
# -C:表示change,change到内核源码里面去编译
# M=$( ):指定了要编译驱动的源码目录;
#因此就该程序会再次进入我们这个makefile文件进行执行
#接下来继续去判断 KERNELRELEASE 变量.......
clean:
make -C $(KERN_DIR) M=$(PWD) clean
endif
2.5、调试运行
1、先make,需要指定管理员权限,细节makefile首字母应该大写
2、通过dmesg命令查看日志信息,觉得信息太多可以使用 -c 先删除之前信息再查看
可以看见我们动态申请的设备号为,主设备号240,次设备号0
3、使用mknod创建对应设备文件,给上层用户提供接口
4、使用insmod加载驱动,rmmod可以卸载驱动
5、编译运行应用函数
一直提示打不开,后面分析应该是,用sudo临时用管理员用户不行
6、su 切换到管理员用户,重复一次
大功告成!最后删除垃圾
3、例2
3.1、目标
使用简单的缓冲区,简单的实现字符设备的读写模拟
3.2、驱动函数
#include <linux/init.h> //module_init(),module_exit()
#include <linux/module.h> //MODULE_LICENSE(), MODULE_AUTHOR()
//#include <linux/fs.h> //alloc_chrdev_region() file_operations()
#include <linux/cdev.h> //cdev_alloc()、cdev_init()、cdev_add()、cdev_del()
//#include <linux/device.h> //class_create()
//#include <linux/kdev_t.h> //MKDEV MINOR MAJORs
#include <linux/ide.h> //copy_to_user()、copy_from_user()、kfree()
#define BASE_MINOR 0 //次设备号从0开始计数
#define DEV_COUNT 1 //1的含义是:本驱动能驱动1个同类设备
#define DEV_NAME "chrDev" //字符设备名称,到时候生成的节点名就是/dev/chrDev
#define BUF_LEN 128 //测试数据缓冲区大小
dev_t devNo; //设备号 由主设备号12bit + 20bit次设备组成
struct cdev *pCDev; //字符设备
struct class *pClass; //设备类
struct device *pDevice; //设备,Linux下的设备都用struct device结构体进行描述
//对于Linux设备我的另外博客有介绍
//数据缓冲区
static int dataLen = BUF_LEN; //数据缓冲区长度
static char kernelBuf[BUF_LEN] = "Hello Char Device Driver"; //用来作为数据缓冲区
//对应系统调用open
static int CharDevOpen(struct inode *inode, struct file *filp)
{
//调试相关的宏,我的另外博客有介绍
printk(KERN_INFO "[%s:%s-%d] CharDevOpen\r\n", __FILE__, __func__, __LINE__);
return 0;
}
//对应系统调用read
static ssize_t CharDevRead (struct file *file, char __user *buf,
size_t cnt, loff_t *offt)
{
int ret = 0;
int len = (cnt > dataLen) ? dataLen : cnt;
ret = copy_to_user(buf, kernelBuf, len);//将内核缓冲区数据拷贝到用户缓冲区
if (!ret) {
ret = len;
printk(KERN_INFO "kernel copy_to_user: %.128s\r\n", kernelBuf);
} else {
printk(KERN_INFO "kernel copy_to_user fail, %d\r\n", ret);
}
return ret;
}
//对应系统调用write
static ssize_t CharDevWrite (struct file *file, const char __user *buf,
size_t cnt, loff_t *offt)
{
int ret = 0;
int len = (cnt > dataLen) ? dataLen : cnt;
ret = copy_from_user(kernelBuf, buf, len);//将用户缓冲区数据拷贝到内核缓冲区
if (!ret) {
dataLen = len;
ret = len;
printk(KERN_INFO "kernel copy_from_user: %.128s\r\n", kernelBuf);
} else {
ret = 0;
printk(KERN_INFO "kernel copy_from_user fail, %d\r\n", ret);
}
return ret;
}
//对应系统调用close
static int CharDevRelease(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "[%s:%s-%d] CharDevRelease\r\n", __FILE__, __func__, __LINE__);
return 0;
}
//操作集
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = CharDevOpen,
.read = CharDevRead,
.write = CharDevWrite,
.release = CharDevRelease,
};
//入口函数, 设备初始化
static int __init CharDevInit(void)
{
int ret;
//1、申请设备号
#if 0 //动态申请
ret = alloc_chrdev_region(&devNo, BASE_MINOR, DEV_COUNT, DEV_NAME);
if (0 > ret) {
printk(KERN_ERR "[%s:%s-%d] alloc_chrdev_region fail\r\n", __FILE__, __func__, __LINE__);
goto err1;
}
如果是动态申请的设备号,可以使用函数 MAJOR(test_char_dev.devid); /* 获取主设备号 */
MINOR(test_char_dev.devid); /* 获取次设备号 */
#else //静态申请--也就是指定主设备号
#define CHRDEV_MAJOR 210
devNo = MKDEV(CHRDEV_MAJOR, BASE_MINOR);
ret = register_chrdev_region(devNo, DEV_COUNT, DEV_NAME);
if (0 > ret) {
printk(KERN_ERR "[%s:%s-%d] register_chrdev_region fail\r\n", __FILE__, __func__, __LINE__);
goto err1;
}
#endif
//2、申请cdev
pCDev = cdev_alloc();
if (NULL == pCDev) {
printk(KERN_ERR "[%s:%s-%d] cdev_alloc fail\r\n", __FILE__, __func__, __LINE__);
ret = (-ENOMEM);
goto err2;
}
//3、初始化cdev
cdev_init(pCDev, &fops);
pCDev->owner = THIS_MODULE;
//4、添加cdev
ret = cdev_add(pCDev, devNo, DEV_COUNT);
if (0 > ret) {
printk(KERN_ERR "[%s:%s-%d] cdev_alloc fail\r\n", __FILE__, __func__, __LINE__);
goto err3;
}
//5、创建class
pClass = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(pDevice)) {
printk(KERN_ERR "[%s:%s-%d] class_create fail\r\n", __FILE__, __func__, __LINE__);
ret = PTR_ERR(pClass);
goto err4;
}
//6、创建device
pDevice = device_create(pClass, NULL, devNo, NULL, DEV_NAME);
if (IS_ERR(pDevice)) {
printk(KERN_ERR "[%s:%s-%d] device_create fail\r\n", __FILE__, __func__, __LINE__);
ret = PTR_ERR(pDevice);
goto err5;
}
printk(KERN_INFO "[%s:%s-%d] Init success dev:%s, MAJOR:%d, MINOR:%d.\r\n", __FILE__, __func__, __LINE__,
DEV_NAME, MAJOR(devNo), MINOR(devNo));
return 0;
//错误打印
err5:
class_destroy(pClass);
err4:
cdev_del(pCDev);
err3:
kfree(pCDev);
err2:
unregister_chrdev_region(devNo, DEV_COUNT);
err1:
return ret;
}
//出口函数 释放资源,注销设备
static void __exit CharDevUninit(void)
{
device_destroy(pClass, devNo);
class_destroy(pClass);
cdev_del(pCDev);
unregister_chrdev_region(devNo, DEV_COUNT);
printk(KERN_INFO "[%s:%s-%d]\r\n", __FILE__, __func__, __LINE__);
}
module_init(CharDevInit);
module_exit(CharDevUninit);
MODULE_LICENSE("GPL"); //必要的协议声明
MODULE_AUTHOR("Smile");
MODULE_DESCRIPTION("Char Device Driver Demo");
3.3、应用函数
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define DEV_NAME "/dev/chrDev"
int main(int argc, char *argv[])
{
int ret;
int fd;
char buf[256] = {0};
fd = open(DEV_NAME, O_RDWR);
if (0 > fd) {
printf("open dev fail: %s\r\n", DEV_NAME);
perror("cause:");
return -1;
} else {
printf("open dev success: %s\r\n", DEV_NAME);
}
//1 读一下当前数据
ret = read(fd, buf, sizeof(buf));
printf("1 read ret:%d, buf:[%s]\r\n", ret, buf);
//2 写入自己的数据
snprintf(buf, sizeof(buf), "Hello Driver %s", __TIME__);
ret = write(fd, buf, strlen(buf));
printf("2 write ret:%d, len:%ld, data:%s\r\n", ret, strlen(buf), buf);
//3 再次读数据,应该是步骤2写入的数据
memset(buf, 0x0, sizeof(buf));
ret = read(fd, buf, sizeof(buf));
printf("3 read ret:%d, buf:[%s]\r\n", ret, buf);
close(fd);
return 0;
}
3.4、通用Makefile
ifneq ($(KERNELRELEASE),)
obj-m := CharDriver.o #具体修改只需要该.o之前的变量名和内核源码目录路径
else
KERNEL_DIR := /lib/modules/$(shell uname -r)/build # 取内核的build目录给KDIR
PWD := $(shell pwd) # 表示取当前的pwd值赋给PWD变量
all:
make -C $(KERNEL_DIR) M=$(PWD)
# -C:表示change,change到内核源码里面去编译
# M=$( ):指定了要编译驱动的源码目录;
#因此就该程序会再次进入我们这个makefile文件进行执行
#接下来继续去判断 KERNELRELEASE 变量.......
clean:
make -C $(KERN_DIR) M=$(PWD) clean
endif
3.5、调试运行
1、su 切换到管理员用户
2、make编译驱动文件和用户文件
3、使用insmod加载驱动,rmmod可以卸载驱动
4、 因为我们是使用函数自动创建设备文件,所以不需要在手动创建接口
5、执行测试程序
大功告成!!!
4、例3
前提小知识
一、应用程序中的ioctl接口
首先,我们需要规定一些命令码,这些命令码在应用程序和驱动程序中需要保持一致。应用程序只需向驱动程序下发一条指令码,用来通知它执行哪条命令。如何解读这条指令和怎么实现相关操作,就是驱动程序自己要做的事。
应用程序的接口函数为ioctl,参考官方文档,函数原型为
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
下面我们解释各个参数的含义。
- 1)fd是文件描述符。当我们的设备作为特殊文件被open()函数打开后,会返回一个文件描述符,通过操作这个文件描述符达到操作设备文件的目的。
- 2)request是命令码,应用程序通过下发命令码来控制驱动程序完成对应操作。
- 3)第三个参数“…”是可变参数arg,一些情况下应用程序需要向驱动程序传参,参数就通过ag来传递。ioctl函数中的“…”只能传递一个参数,但内核不会检查这个参数的类型。那么,就有两种传参方式:只传一个整数,传递一个指针。
如果ioctl执行成功,它的返回值就是驱动程序中ioctl接口给的返回值,驱动程序可以通过返回值向用户程序传参。但驱动程序最好返回一个非负数,因为用户程序中的ioctl运行失败时一定会返回-1并设置全局变量errorno。
errono不同的值代表的含义如下:
- EBADF:fd是一个无效的文件描述符。
- EFAULT:在arg是指针的前提下,argp指向一个不可访问的内存空间。
- EINVAL:request或argp是无效的。
- ENOTTY:fd没有关联到一个字符特殊设备,或该request不适用于文件描述符fd引用的对象类型。(说人话就是fd没有指向一个字符设备,或fd指向的文件不支持ioctl操作)
因此,在用户空间调用ioctl时,可以使用如下的错误判断处理。包括的两个头文件,string.h声明了strerror函数,errno.h定义了错误码errno。
#include <string.h>
#include <errno.h>
int ret;
ret = ioctl(fd, MYCMD);
if (ret == -1)
printf("ioctl: %s\n", strerror(errno));
二、驱动程序中的ioctl接口
在驱动程序的ioctl函数体中,实现了一个switch-case结构,每一个case对应一个命令码,case内部是驱动程序实现该命令的相关操作。
ioctl的实现函数要传递给file_operations结构体中对应的函数指针,函数原型为
#include <linux/ioctl.h>
long (*unlocked_ioctl) (struct file * fp, unsigned int request, unsigned long args);
long (*compat_ioctl) (struct file * fp, unsigned int request, unsigned long args);
unlocked_ioctl在无大内核锁(BKL)的情况下调用。64位用户程序运行在64位的kernel,或32位的用户程序运行在32位的kernel上,都是调用unlocked_ioctl函数。
compat_ioctl是64位系统提供32位ioctl的兼容方法,也在无大内核锁的情况下调用。即如果是32位的用户程序调用64位的kernel,则会调用compat_ioctl。如果驱动程序没有实现compat_ioctl,则用户程序在执行ioctl时会返回错误Not a typewriter。
另外,如果32位用户态和64位内核态发生交互时,第三个参数的长度需要保持一致,否则交互协议会出错。
int (*ioctl) (struct inode *inode, struct file *fp, unsigned int request, unsigned long args);
在2.6.35.7及以前的内核版本中,file_operations还定义了ioctl()接口,与unlocked_ioctl是等价的。但是在2.6.36以后就不再支持这个接口,全部使用unlocked_ioctl了。
以上函数参数的含义如下。
- 1)inode和fp用来确定被操作的设备。
- 2)request就是用户程序下发的命令。
- 3)args就是用户程序在必要时传递的参数。
返回值:可以在函数体中随意定义返回值,这个返回值也会被直接返回到用户程序中。通常使用非负数表示正确的返回,而返回一个负数系统会判定为ioctl调用失败。
三、用户与驱动之间的ioctl协议构成
也就是request或cmd,本质上就是一个32位数字,理论上可以是任何一个数,但为了保证命令码的唯一性,linux定义了一套严格的规定,通过计算得到这个命令吗数字。linux将32位划分为四段,如下图。
含义如下。
- 1)dir,即direction,表示ioctl命令的访问模式,分为无数据(_IO)、读数据(_IOR)、写数据(_IOW)、读写数据(_IOWR)四种模式。
- 2)type,即device type,表示设备类型,也可翻译成“幻数”或“魔数”,可以是任意一个char型字符,如’a’、‘b’、‘c’等,其主要作用是使ioctl命令具有唯一的设备标识。不过在内核中’w’、‘y’、'z’三个字符已经被使用了。
- 3)nr,即number,命令编号/序数,取值范围0~255,在定义了多个ioctl命令的时候,通常从0开始顺次往下编号。
- 4)size,涉及到ioctl的参数arg,占据13bit或14bit,这个与体系有关,arm使用14bit。用来传递arg的数据类型的长度,比如如果arg是int型,我们就将这个参数填入int,系统会检查数据类型和长度的正确性。
在上面的四个参数都需要用户自己定义,linux系统提供了宏可以使程序员方便的定义ioctl命令码。
include/uapi/asm-generic/ioctl.h
--------------------------------------------
/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
分别对应了四个dir:
- _IO(type, nr):用来定义不带参数的ioctl命令。
- _IOR(type,nr,size):用来定义用户程序向驱动程序写参数的ioctl命令。
- _IOW(type,nr,size):用来定义用户程序从驱动程序读参数的ioctl命令。
- _IOWR(type,nr,size):用来定义带读写参数的驱动命令。
当然了,系统也定义反向解析ioctl命令的宏。
include/uapi/asm-generic/ioctl.h
--------------------------------------------
/* used to decode ioctl numbers */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK
4.1、目标
本例中,我们让ioctl传递三个命令,分别是一个无参数、写参数、读参数三个指令。首先我们需要确定两个头文件,命名为ioctl_test.h和user_ioctl.h,用来分别定义内核空间和用户空间下的命令码协议。两个头文件中除了引用不同的头文件外,其他内容需要完全一致,以保证协议的一致性。
我们使用字符’a’作为幻数,三个命令的作用分别是用户程序让驱动程序打印一句话,用户程序从驱动程序读一个int型数,用户程序向驱动程序写一个int型数。
4.2、驱动函数
#include <linux/init.h> //module_init(),module_exit()
#include <linux/module.h> //MODULE_LICENSE(), MODULE_AUTHOR()
//#include <linux/fs.h> //alloc_chrdev_region() file_operations()
#include <linux/cdev.h> //cdev_alloc()、cdev_init()、cdev_add()、cdev_del()
//#include <linux/device.h> //class_create()
//#include <linux/kdev_t.h> //MKDEV MINOR MAJORs
#include <linux/ide.h> //copy_to_user()、copy_from_user()、kfree()
#define CMD_IOC_MAGIC 'a'
#define CMD_IOC_0 _IO(CMD_IOC_MAGIC, 0)
#define CMD_IOC_1 _IOR(CMD_IOC_MAGIC, 1, int)
#define CMD_IOC_2 _IOW(CMD_IOC_MAGIC, 2, int)
#define BASE_MINOR 0 //次设备号从0开始计数
#define DEV_COUNT 1 //1的含义是:本驱动能驱动1个同类设备
#define DEV_NAME "chrDev" //字符设备名称,到时候生成的节点名就是/dev/chrDev
dev_t devNo; //设备号 由主设备号12bit + 20bit次设备组成
struct cdev *pCDev; //字符设备
struct class *pClass; //设备类
struct device *pDevice; //设备,Linux下的设备都用struct device结构体进行描述
//对于Linux设备我的另外博客有介绍
//系统对于open
static int demo_open(struct inode *ind, struct file *fp)
{
printk("demo open\n");
return 0;
}
//系统对于close
static int demo_release(struct inode *ind, struct file *fp)
{
printk("demo release\n");
return 0;
}
//系统对于ioctl
static long demo_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
int rc = 0;
int arg_w;
const int arg_r = 566;
if (_IOC_TYPE(cmd) != CMD_IOC_MAGIC) {
pr_err("%s: command type [%c] error.\n", __func__, _IOC_TYPE(cmd));
return -ENOTTY;
}
switch(cmd) {
case CMD_IOC_0:
printk("cmd 0: no argument.\n");
rc = 0;
break;
case CMD_IOC_1:
printk("cmd 1: ioc read, arg = %d.\n", arg_r);
arg = arg_r;
rc = 1;
break;
case CMD_IOC_2:
arg_w = arg;
printk("cmd 2: ioc write, arg = %d.\n", arg_w);
rc = 2;
break;
default:
pr_err("%s: invalid command.\n", __func__);
return -ENOTTY;
}
return rc;
}
static struct file_operations fops = {
.open = demo_open,
.release = demo_release,
.unlocked_ioctl = demo_ioctl,
};
static int demo_init(void)
{
int ret;
//1、申请设备号
#if 0 //动态申请
ret = alloc_chrdev_region(&devNo, BASE_MINOR, DEV_COUNT, DEV_NAME);
if (0 > ret) {
printk(KERN_ERR "[%s:%s-%d] alloc_chrdev_region fail\r\n", __FILE__, __func__, __LINE__);
goto err1;
}
如果是动态申请的设备号,可以使用函数 MAJOR(test_char_dev.devid); /* 获取主设备号 */
MINOR(test_char_dev.devid); /* 获取次设备号 */
#else //静态申请--也就是指定主设备号
#define CHRDEV_MAJOR 210
devNo = MKDEV(CHRDEV_MAJOR, BASE_MINOR);
ret = register_chrdev_region(devNo, DEV_COUNT, DEV_NAME);
if (0 > ret) {
printk(KERN_ERR "[%s:%s-%d] register_chrdev_region fail\r\n", __FILE__, __func__, __LINE__);
goto err1;
}
#endif
//2、申请cdev
pCDev = cdev_alloc();
if (NULL == pCDev) {
printk(KERN_ERR "[%s:%s-%d] cdev_alloc fail\r\n", __FILE__, __func__, __LINE__);
ret = (-ENOMEM);
goto err2;
}
//3、初始化cdev
cdev_init(pCDev, &fops);
pCDev->owner = THIS_MODULE;
//4、添加cdev
ret = cdev_add(pCDev, devNo, DEV_COUNT);
if (0 > ret) {
printk(KERN_ERR "[%s:%s-%d] cdev_alloc fail\r\n", __FILE__, __func__, __LINE__);
goto err3;
}
//5、创建class
pClass = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(pDevice)) {
printk(KERN_ERR "[%s:%s-%d] class_create fail\r\n", __FILE__, __func__, __LINE__);
ret = PTR_ERR(pClass);
goto err4;
}
//6、创建device
pDevice = device_create(pClass, NULL, devNo, NULL, DEV_NAME);
if (IS_ERR(pDevice)) {
printk(KERN_ERR "[%s:%s-%d] device_create fail\r\n", __FILE__, __func__, __LINE__);
ret = PTR_ERR(pDevice);
goto err5;
}
printk(KERN_INFO "[%s:%s-%d] Init success dev:%s, MAJOR:%d, MINOR:%d.\r\n", __FILE__, __func__, __LINE__,
DEV_NAME, MAJOR(devNo), MINOR(devNo));
return 0;
//错误打印
err5:
class_destroy(pClass);
err4:
cdev_del(pCDev);
err3:
kfree(pCDev);
err2:
unregister_chrdev_region(devNo, DEV_COUNT);
err1:
return ret;
}
static void demo_exit(void)
{
device_destroy(pClass, devNo);
class_destroy(pClass);
cdev_del(pCDev);
unregister_chrdev_region(devNo, DEV_COUNT);
printk(KERN_INFO "[%s:%s-%d]\r\n", __FILE__, __func__, __LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");//必要的协议声明
MODULE_AUTHOR("Smile");
4.3、应用函数
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define CMD_IOC_MAGIC 'a'
#define CMD_IOC_0 _IO(CMD_IOC_MAGIC, 0)
#define CMD_IOC_1 _IOR(CMD_IOC_MAGIC, 1, int)
#define CMD_IOC_2 _IOW(CMD_IOC_MAGIC, 2, int)
int main()
{
int rc;
int arg_r;
const int arg_w = 233;
int fd = open("/dev/chrDev", O_RDWR);
if (fd < 0) {
printf("open file failed!\n");
return -1;
}
rc = ioctl(fd, CMD_IOC_0);
printf("rc = %d.\n", rc);
rc = ioctl(fd, CMD_IOC_1, arg_r);
printf("ioc read arg = %d, rc = %d.\n", arg_r, rc);
rc = ioctl(fd, CMD_IOC_2, arg_w);
printf("ioc write arg = %d, rc = %d.\n", arg_w, rc);
close(fd);
return 0;
}
4.4、通用Makefile
ifneq ($(KERNELRELEASE),)
obj-m := myioctl.o #具体修改只需要该.o之前的变量名和内核源码目录路径
else
KERNEL_DIR := /lib/modules/$(shell uname -r)/build # 取内核的build目录给KDIR
PWD := $(shell pwd) # 表示取当前的pwd值赋给PWD变量
all:
make -C $(KERNEL_DIR) M=$(PWD)
# -C:表示change,change到内核源码里面去编译
# M=$( ):指定了要编译驱动的源码目录;
#因此就该程序会再次进入我们这个makefile文件进行执行
#接下来继续去判断 KERNELRELEASE 变量.......
clean:
make -C $(KERN_DIR) M=$(PWD) clean
endif
4.5、调试运行
1、su进入管理员用户
2、make编译驱动文件和用户文件
3、使用insmod加载驱动,rmmod可以卸载驱动
4、 因为我们是使用函数自动创建设备文件,所以不需要在手动创建接口
5、执行测试程序
大功告成!!!