Linux·字符设备驱动实例-基于通用摸版

具体理论分析字符设备 

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、执行测试程序

大功告成!!! 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值