Linux内核模块编程-与设备文件对话

与设备文件对话

在上一篇博文中,我们编写了一个字符设备驱动,简单的回顾下我们编写的流程:

  • 实现open/close/read/write四个操作设备文件的函数
  • 填充file_operations结构体
  • 注册设备和指明操作设备的file_operations

与设备之间可以通过上面提到的几个函数来进行通信,但是对于某些设备来说,设备的操作且无法通过上面提到的几个函数来实现
ioctl的一些简介:
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的参数个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。

来自百度百科

ioctl的功能:
控制I/O设备 ,提供了一种获得设备信息和向设备发送控制参数的手段。用于向设备发控制和配置命令 ,有些命令需要控制参数,这些数据是不能用read/write 读写的,称为Out-of-band数据。也就是说,read/ write 读写的数据是in-band数据,是I/O操作的主体,而ioctl 命令传送的是控制信息,其中的数据是辅助的数据。

来自百度百科

要想全面的去控制一个设备,那么在内核中实现ioctl和标准的VFS文件操作接口,这都应该是必须的。那么本篇博文其实主要讲的就是如何通过ioctl来和设备文件通信。

开始去实现

ioctl

ioctl中的其实最重要的就是cmd和参数,分别对比下用户空间和内核空间关于ioctl相关的函数原型

用户空间:
   #include <sys/ioctl.h>
   int ioctl(int fd, int cmd, ...);
fd是设备文件的描述符
cmd是发送给设备的命令,如果这个命令带有参数,后面还会接参数。

内核空间:
int (struct file *file,unsigned int ioctl_num,unsigned long ioctl_param)
file对应打开的设备文件
ioctl_num 对应用户空间的cmd
ioctl_param则对应于cmd指定的参数

那么就存在一个问题了,怎么知道哪些cmd带有参数,哪写不带参数,以及参数的类型。
cmd可以随便定义吗?
其实cmd的定义是有规定的,cmd大小为32位,这32位分成了四个域:
bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
bit29~bit15 14位为 “数据大小” 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit20~bit08 8位为 “魔数”(也称为”幻数”)区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit07~bit00 8位为 “区别序号” 区,是区分命令的命令顺序序号。
那么如果要去定义个cmd,那么就的按照这个规定来定义,需要自己去计算。幸好在内核空间提供了一系列的
函数帮我们生成cmd,还有一些列的函数可以帮我们解析出cmd的每一个域。

#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \
     ((type) << _IOC_TYPESHIFT) | \
     ((nr)   << _IOC_NRSHIFT) | \
     ((size) << _IOC_SIZESHIFT))
//一系列用来定义cmd的函数
/* 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)))
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
//一系列用来解析cmd的函数
/* 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)

介绍完ioctl后,那就开始去定义一些cmd吧

chardev1.h
#ifndef CHARDEV_H
#define CHARDEV_H
#include <asm-generic/ioctl.h>
#define IO_MAGIC 'z' //这是魔数,参照上面的解释,通常是a-z A-Z范围内
#define IOCTL_SET_MSG _IOR(IO_MAGIC,0,char*) //定义了一个接受char*参数的命令码
#define IOCTL_GET_MSG _IOR(IO_MAGIC,1,char*)
#define IOCTL_GET_NTH_BYTE _IOWR(IO_MAGIC,2,int) //定义了一个接受int型参数的命令码
#define DEVICE_FILE_NAME "char_dev"
#endif

这个chardev1.h 不仅是给内核模块使用的,用户空间也要使用,因为要给设备发ioctl cmd的时候,需要
知道cmd的定义。所以这个文件很重要。

老生常谈的模块初始化和注销

和我上一篇博文,关于字符设备驱动的代码基本一样,只不过这里是通过ioctl来控制读写的。

#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80
static int Device_Open = 0;
static int MAJOR_NUM;
static char Message[BUF_LEN];
static char *Message_Ptr;
int init_module(void)
{
//随机生成
    MAJOR_NUM = register_chrdev(0,DEVICE_NAME,&Fops);
    if(MAJOR_NUM < 0) {
        printk("%s failed with %d\n",
            "Sorry,registering the character device",MAJOR_NUM);
        return MAJOR_NUM;
    }   
    printk("%s The major device number is %d\n",
         "Register is a success",MAJOR_NUM);
    printk("If you want to talk to the device driver,\n");
    printk("you'll have to create a device file\n");
    printk("we suggest you use:\n");
    printk("mknod %s c %d 0\n",DEVICE_NAME,MAJOR_NUM);
    printk("the device file name is important,because\n");
    printk("the ioctl program assumes that's the \n");
    printk("file you'll use\n");
    return 0;
}
void cleanup_module(void)
{
    unregister_chrdev(MAJOR_NUM,DEVICE_NAME);
}

read/write/open/release的实现

static int device_open(struct inode *inode,struct file *file)
{
    printk("device_open(%p)\n",file);
    if (Device_Open)
        return -EBUSY;

    Device_Open++;
    Message_Ptr = Message;
    try_module_get(THIS_MODULE);
    return SUCCESS;
}

static int device_release(struct inode *inode,struct file *file)
{
    printk("device_release(%p,%p)\n",inode,file);
    Device_Open--;
    module_put(THIS_MODULE);
    return SUCCESS;
}

static ssize_t device_read(struct file *file,
                            char __user *buffer,
                            size_t length,
                            loff_t *offset)
{
    int bytes_read = 0;
    printk("device_read(%p,%p,%d)\n",file,buffer,length);

    if(*Message_Ptr == 0)
        return 0;
    while(length && *Message_Ptr) {
        put_user(*(Message_Ptr++),buffer++);
        length--;
        bytes_read++;
    }
    return bytes_read;
}

static ssize_t device_write(struct file *file,
                            const char __user *buffer,
                            size_t length,
                            loff_t *offset)
{
    int i;
    for(i = 0;i < length && i < BUF_LEN;i++)
        get_user(Message[i],buffer+i);

    Message_Ptr = Message;
    return i;
}

最重要的ioctl的实现

int device_ioctl(struct file *file,
                unsigned int ioctl_num,
                unsigned long ioctl_param)
{
    int i;
    char *temp;
    char ch;
    //典型的switch case分发命令
    switch(ioctl_num) {
        case IOCTL_SET_MSG:
            temp = (char*)ioctl_param; //获取到参数
            get_user(ch,temp);         //不断读取数据到ch中,统计ioctl_param的长度用i表示
            for(i = 0;ch && i < BUF_LEN;i++,temp++)
                get_user(ch,temp);
             //最终调用write来将用户态的数据写入到设备中
            device_write(file,(char*)ioctl_param,i,0);
            break;

        case IOCTL_GET_MSG:
            i = device_read(file,(char*)ioctl_param,99,0);//写入数据,最多写入99字节的数据
            put_user('\0',(char*)ioctl_param+i); //设置用户空间bu中的第i个位置为'\0'用来结束
            break;

        case IOCTL_GET_NTH_BYTE:
            return Message[ioctl_param];//获取指定位置处的字符
    }
    return SUCCESS;
}

注册VFS接口

struct file_operations Fops = { 
    .read = device_read,
    .write = device_write,
    .unlocked_ioctl = device_ioctl, //这里需要注意,用的是unlocked_ioctl而不是ioctl
    //因为在kernel 2.6.36 中已经完全删除了struct file_operations中的ioctl 函数指针,取而代之的是unlocked_ioctl 
    .open = device_open,
    .release = device_release,
};

用户空间程序测试

首先需要编译,产生内核模块。然后载入内核模块,根据打印的信息获取分配的主设备号,然后创建该设备

insmod chardev1.ko
dmesg查看打印信息
Register is a success The major device number is 247
If you want to talk to the device driver,
you'll have to create a device file
we suggest you use:
mknod char_dev c 247 0
the device file name is important,because
the ioctl program assumes that's the  
file you'll use
创建字符设备
mknod /dev/char_dev c 247 0

用户空间程序测试

#include "chardev1.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void ioctl_set_msg(int file_desc,char *message)
{
    int ret_val;
    ret_val = ioctl(file_desc,IOCTL_SET_MSG,message);
    if (ret_val < 0) {
        printf("ioctl_set_msg failed:%d\n",ret_val);
        exit(-1);
    }   
}
void ioctl_get_msg(int file_desc)
{
    int ret_val;
    char message[100];
    ret_val = ioctl(file_desc,IOCTL_GET_MSG,message);
    if (ret_val < 0) {
        printf("ioctl_get_msg failed:%d\n",ret_val);
        exit(-1);
    }
    printf("get_msg message:%s\n",message);
}
void ioctl_get_nth_byte(int file_desc)
{
    int i;
    char c='m'; //最好给个处置,在我的机器上居然好几次初值都是0.导致while循环跳出
    printf("get_nth_byte message:");
    i = 0;
    while(c != 0) {
        c = ioctl(file_desc,IOCTL_GET_NTH_BYTE,i++);
        if(c < 0) {
            printf("ioctl_get_nth_byte failed at the %d'th byte:\n",i);
            exit(-1);
        }
        putchar(c);
    }
    putchar('\n');
}

int main()
{
    int file_desc,ret_val;
    char *msg = "Message passwd by ioctl\n";
    file_desc = open("/dev/char_dev",0);
    if(file_desc < 0) {
        printf("can't open device file: %s\n",DEVICE_FILE_NAME);
        exit(-1);
    }
// ioctl_get_nth_byte(file_desc);
// ioctl_get_msg(file_desc);
// ioctl_set_msg(file_desc,msg);

    close(file_desc);
}

对于上面注释的三条语句,可以依次打开 查看效果,ioctl_get_msg获取设备数据的,但是第一次获取是空
通过ioctl_set_msg设置后,就可以获取到msg指向的内容。ioctl_get_nth_byte则是一个字符字符的获取打印。

你可能感兴趣的:(编程,linux,函数,kernel)