linux内核模块编程7

Chapter 7. Talking To Device Files
与设备文件对话 (writes and IOCTLs)
设备文件是用来代表相对应的硬件设备。绝大多数的硬件设备是用来进行输出和输入操作的, 所以在内核中肯定有内核从进程中获得发送到设备的输出的机制。这是通过打开一个设备文件然后 向其中进行写操作来实现的,如同对普通文件的写操作。在下面的的例子中,这是通过 device_write实现的。

但这并不总是够用。设想你有一个通过串口连接的调制解调器(即使你使用的是内置调制解调器, 对于CPU来说同样也是通过连接在串口上来实现工作的)。通常我们通过打开一个设备文件向调制解调器 发送信息(将要通过通信线路传输的指令或数据)或读取信息(从通信线路中返回的响应指令或数据)。 但是,我们如何设置同串口对话的速率,也就是向串口传输数据的速率这个问题仍然没有解决。

解决之道是在Unix系统中的函数ioctl(Input Output ConTroL的简写)。 每个设备可以有自己的ioctl命令,通过读取ioctl's 可以从进程中向内核发送信息,或写ioctl's向进程返回信息 [1],或者两者都是,或都不是。函数ioctl 调用时需要三个参数:合适的设备文件的文件描述符,ioctl号,和一个可以被一个任务使用来 传递任何东西的long类型的参数[2]

ioctl号是反映主设备号,ioctl的种类,对应的命令和参数类型的数字。它通常是通过在头文件中宏调用 (_IO, _IOR, _IOW 或_IOWR,取决于其种类)来建立的。该头文件应该被使用 ioctl的用户程序包含(这样它们就可以生成正确的ioctl's) 和内核驱动模块包含(这样模块才能理解它)。在下面的例子中,头文件为chardev.h,源程序为ioctl.c。

即使你只想在自己的模块中使用ioctls,你最好还是接收正式的 ioctl标准,这样当你意外的使用别人的ioctls, 或别人使用你的时,你会知道有错误发生。详情参见内核代码目录树下的文件 Documentation/ioctl-number.txt.

Example 7-1. chardev.c

/*
*  chardev.c - Create an input/output character device
*/

#include <linux/kernel.h>        /* We're doing kernel work */
#include <linux/module.h>        /* Specifically, a module */
#include <linux/fs.h>
#include <asm/uaccess.h>        /* for get_user and put_user */

#include "chardev.h"
#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80

/* 
* Is the device open right now? Used to prevent
* concurent access into the same device 
*/
static int Device_Open = 0;

/* 
* The message the device will give when asked 
*/
static char Message[BUF_LEN];

/* 
* How far did the process reading the message get?
* Useful if the message is larger than the size of the
* buffer we get to fill in device_read. 
*/
static char *Message_Ptr;

/* 
* This is called whenever a process attempts to open the device file 
*/
static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
        printk("device_open(%p)\n", file);
#endif

        /* 
         * We don't want to talk to two processes at the same time 
         */
        if (Device_Open)
                return -EBUSY;

        Device_Open++;
        /*
         * Initialize the message 
         */
        Message_Ptr = Message;
        try_module_get(THIS_MODULE);
        return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
        printk("device_release(%p,%p)\n", inode, file);
#endif

        /* 
         * We're now ready for our next caller 
         */
        Device_Open--;

        module_put(THIS_MODULE);
        return SUCCESS;
}

/* 
* This function is called whenever a process which has already opened the
* device file attempts to read from it.
*/
static ssize_t device_read(struct file *file,        /* see include/linux/fs.h   */
                           char __user * buffer,        /* buffer to be
                                                         * filled with data */
                           size_t length,        /* length of the buffer     */
                           loff_t * offset)
{
        /* 
         * Number of bytes actually written to the buffer 
         */
        int bytes_read = 0;

#ifdef DEBUG
        printk("device_read(%p,%p,%d)\n", file, buffer, length);
#endif

        /* 
         * If we're at the end of the message, return 0
         * (which signifies end of file) 
         */
        if (*Message_Ptr == 0)
                return 0;

        /* 
         * Actually put the data into the buffer 
         */
        while (length && *Message_Ptr) {

                /* 
                 * Because the buffer is in the user data segment,
                 * not the kernel data segment, assignment wouldn't
                 * work. Instead, we have to use put_user which
                 * copies data from the kernel data segment to the
                 * user data segment. 
                 */
                put_user(*(Message_Ptr++), buffer++);
                length--;
                bytes_read++;
        }

#ifdef DEBUG
        printk("Read %d bytes, %d left\n", bytes_read, length);
#endif

        /* 
         * Read functions are supposed to return the number
         * of bytes actually inserted into the buffer 
         */
        return bytes_read;
}

/* 
* This function is called when somebody tries to
* write into our device file. 
*/
static ssize_t
device_write(struct file *file,
             const char __user * buffer, size_t length, loff_t * offset)
{
        int i;

#ifdef DEBUG
        printk("device_write(%p,%s,%d)", file, buffer, length);
#endif

        for (i = 0; i < length && i < BUF_LEN; i++)
                get_user(Message[i], buffer + i);

        Message_Ptr = Message;

        /* 
         * Again, return the number of input characters used 
         */
        return i;
}

/* 
* This function is called whenever a process tries to do an ioctl on our
* device file. We get two extra parameters (additional to the inode and file
* structures, which all device functions get): the number of the ioctl called
* and the parameter given to the ioctl function.
*
* If the ioctl is write or read/write (meaning output is returned to the
* calling process), the ioctl call returns the output of this function.
*
*/
int device_ioctl(struct inode *inode,        /* see include/linux/fs.h */
                 struct file *file,        /* ditto */
                 unsigned int ioctl_num,        /* number and param for ioctl */
                 unsigned long ioctl_param)
{
        int i;
        char *temp;
        char ch;

        /* 
         * Switch according to the ioctl called 
         */
        switch (ioctl_num) {
        case IOCTL_SET_MSG:
                /* 
                 * Receive a pointer to a message (in user space) and set that
                 * to be the device's message.  Get the parameter given to 
                 * ioctl by the process. 
                 */
                temp = (char *)ioctl_param;

                /* 
                 * Find the length of the message 
                 */
                get_user(ch, temp);
                for (i = 0; ch && i < BUF_LEN; i++, temp++)
                        get_user(ch, temp);

                device_write(file, (char *)ioctl_param, i, 0);
                break;

        case IOCTL_GET_MSG:
                /* 
                 * Give the current message to the calling process - 
                 * the parameter we got is a pointer, fill it. 
                 */
                i = device_read(file, (char *)ioctl_param, 99, 0);

                /* 
                 * Put a zero at the end of the buffer, so it will be 
                 * properly terminated 
                 */
                put_user('\0', (char *)ioctl_param + i);
                break;

        case IOCTL_GET_NTH_BYTE:
                /* 
                 * This ioctl is both input (ioctl_param) and 
                 * output (the return value of this function) 
                 */
                return Message[ioctl_param];
                break;
        }

        return SUCCESS;
}

/* Module Declarations */

/* 
* This structure will hold the functions to be called
* when a process does something to the device we
* created. Since a pointer to this structure is kept in
* the devices table, it can't be local to
* init_module. NULL is for unimplemented functions. 
*/
struct file_operations Fops = {
        .read = device_read,
        .write = device_write,
        .ioctl = device_ioctl,
        .open = device_open,
        .release = device_release,        /* a.k.a. close */
};

/* 
* Initialize the module - Register the character device 
*/
int init_module()
{
        int ret_val;
        /* 
         * Register the character device (atleast try) 
         */
        ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

        /* 
         * Negative values signify an error 
         */
        if (ret_val < 0) {
                printk("%s failed with %d\n",
                       "Sorry, registering the character device ", ret_val);
                return ret_val;
        }

        printk("%s The major device number is %d.\n",
               "Registeration 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_FILE_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;
}

/* 
* Cleanup - unregister the appropriate file from /proc 
*/
void cleanup_module()
{
        int ret;

        /* 
         * Unregister the device 
         */
        ret = unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

        /* 
         * If there's an error, report it 
         */
        if (ret < 0)
                printk("Error in module_unregister_chrdev: %d\n", ret);
}
Example 7-2. chardev.h

/*
*  chardev.h - the header file with the ioctl definitions.
*
*  The declarations here have to be in a header file, because
*  they need to be known both to the kernel module
*  (in chardev.c) and the process calling ioctl (ioctl.c)
*/

#ifndef CHARDEV_H
#define CHARDEV_H

#include <linux/ioctl.h>

/* 
* The major device number. We can't rely on dynamic 
* registration any more, because ioctls need to know 
* it. 
*/
#define MAJOR_NUM 100

/* 
* Set the message of the device driver 
*/
#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)
/*
* _IOR means that we're creating an ioctl command 
* number for passing information from a user process
* to the kernel module. 
*
* The first arguments, MAJOR_NUM, is the major device 
* number we're using.
*
* The second argument is the number of the command 
* (there could be several with different meanings).
*
* The third argument is the type we want to get from 
* the process to the kernel.
*/

/* 
* Get the message of the device driver 
*/
#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)
/* 
* This IOCTL is used for output, to get the message 
* of the device driver. However, we still need the 
* buffer to place the message in to be input, 
* as it is allocated by the process.
*/

/* 
* Get the n'th byte of the message 
*/
#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)
/* 
* The IOCTL is used for both input and output. It 
* receives from the user a number, n, and returns 
* Message[n]. 
*/

/* 
* The name of the device file 
*/
#define DEVICE_FILE_NAME "char_dev"

#endif
Example 7-3. ioctl.c

/*
*  ioctl.c - the process to use ioctl's to control the kernel module
*
*  Until now we could have used cat for input and output.  But now
*  we need to do ioctl's, which require writing our own process.
*/

/* 
* device specifics, such as ioctl numbers and the
* major device file. 
*/
#include "chardev.h"

#include <fcntl.h>                /* open */
#include <unistd.h>                /* exit */
#include <sys/ioctl.h>                /* ioctl */

/* 
* Functions for the ioctl calls 
*/

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);
        }
}

ioctl_get_msg(int file_desc)
{
        int ret_val;
        char message[100];

        /* 
         * Warning - this is dangerous because we don't tell
         * the kernel how far it's allowed to write, so it
         * might overflow the buffer. In a real production
         * program, we would have used two ioctls - one to tell
         * the kernel the buffer length and another to give
         * it the buffer to fill
         */
        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);
}

ioctl_get_nth_byte(int file_desc)
{
        int i;
        char c;

        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');
}

/* 
* Main - Call the ioctl functions 
*/
main()
{
        int file_desc, ret_val;
        char *msg = "Message passed by ioctl\n";

        file_desc = open(DEVICE_FILE_NAME, 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);
}
Notes
[1] 注意这儿“读”与“写”的角色再次翻转过来,在ioctl's中读是向内核发送信息, 而写是从内核获取信息。

[2] 这样的表述并不准确。 例如你不能在ioctl中传递一个结构体,但你可以通过传递指向这个结构体的指针实现。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值