字符设备文件

那么,现在我们是原始级的内核程序员,我们知道如何写不做任何事情的内核模块。

我们为自己而骄傲并且高昂起头来。但是不知何故我们感觉到缺了什么东西。患有精神紧张症的模块不是那么有意义。

内核模块同进程对话有两种主要途径。一种是通过设备文件(比如/dev 目录中的文件),另一种是使用proc 文件系统。我们把一些东西写入内核的一个主要原因就是支持一些硬件设备,所以我们从设备文件开始。设备文件的最初目的是允许进程同内核中的设备驱动通信,并且通过它们和物理设备通信(modem,终端,等等)。这种方法的实现如下:

每个设备驱动都对应着一定类型的硬件设备,并且被赋予一个主码。设备驱动的列表和它们的主码可以在in/proc/devices 中找到。每个设备驱动管理下的物理设备也被赋予一个从码。无论这些设备是否真的安装,在/dev 目录中都将有一个文件,称作设备文件,对应着每一个设备。

例如,如果你进行ls –l /dev/hd[ab] *操作,你将看见可能联结到某台机器上的所有的IDE硬盘分区。注意它们都使用了同一个主码,3,但是从码却互不相同。(声明:这是在PC 结构上的情况,我不知道在其他结构上运行的linux 是否如此。)

在系统安装时,所有设备文件在mknod 命令下被创建。它们必须创建在/dev 目录下没有技术上的原因,只是一种使用上的便利。如果是为测试目的而创建的设备文件,比如我们这里的练习,可能放在你编译内核模块的的目录下更加合适。

设备可以被分成两类:字符设备和块设备。它们的区别是块设备有一个用于请求的缓冲区,所以它们可以选择用什么样的顺序来响应它们。这对于存储设备是非常重要的,读取相邻的扇区比互相远离的分区速度会快得多。另一个区别是块设备只能按块(块大小对应不同设备而变化)接受输入和返回输出,而字符设备却按照它们能接受的最少字节块来接受输入。大部分设备是字符设备,因为它们不需要这种类型的缓冲。你可以通过观看ls -l 命令的输出中的第一个字符而知道一个设备文件是块设备还是字符设备。如果是b 就是块设备,如果是c 就是字符设备。

这个模块可以被分成两部分:模块部分和设备及设备驱动部分。Init_module 函数调用module_register_chrdev 在内核得块设备表里增加设备驱动。同时返回该驱动所使用的主码。

Cleanup_module 函数撤销设备的注册。

这些操作(注册和注销)是这两个函数的主要功能。内核中的函数不是象进程一样自发运行的,而是通过系统调用,或硬件中断或者内核中的其它部分(只要是调用具体的函数)被进程调用的。所以,当你向内和中增加代码时,你应该把它注册为具体某种事件的句柄,而当你把它删除的时候,你需要注销这个句柄。

设备驱动完全由四个设备_<action〉函数构成,它们在希望通过有主码的设备文件实现一些操作时被调用。内核调用它们的途径是通过file_operation 结构Fops。此结构在设备被注册是创建,它包含指向这四个函数的指针。

另一点我们需要记住的是,我们不能允许管理员随心所欲的删除内核模块。这是因为如果设备文件是被进程打开的,那么我们删除内核模块的时候,要使用这些文件就会导致访问正常的函数(读/写)所在的内存位置。如果幸运,那里不会有其他代码被装载,我们将得到一个恶性的错误信息。如果不行,另一个内核模块会被装载到同一个位置,这将意味着会跳入内核中另一个程序的中间,结果将是不可预料的恶劣。

通常你不希望一个函数做什么事情的时候,会从那个函数返回一个错误码(一个负数)。但这在cleanup_module 中是不可能的,因为它是一个void 型的函数。一旦cleanup_module被调用,这个模块就死掉了。然而有一个计数器记录着有多少个内核模块在使用这个模块,这个计数器称为索引计数器(/proc/modules 中没行的最后一个数字)。如果这个数字不是0,删除就会失败。模块的索引计数器包含在变量mod_use_count_中。有定义好的处理这个变量的宏(MOD_INC_USE_COUNT 和MOD_DEC_USE_COUNT),所以我们一般使用宏而不是直接使用变量mod_use_count_,这样在以后实现变化的时候会带来安全性。

ex chardev.c

/* chardev.c
* Copyright (C) 1998-1999 by Ori Pomerantz
*
* Create a character device (read only)
*/
/* The necessary header files */
/* Standard in kernel modules */
#include <linux/kernel.h> /* We're doing kernel work */
#include <linux/module.h> /* Specifically, a module */
/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif
/* For character devices */
#include <linux/fs.h> /* The character device
                        * definitions are here */
#include <linux/wrapper.h> /* A wrapper which does
                            * next to nothing at
                            * at present, but may
                            * help for compatibility
                            * with future versions
                            * of Linux */
/* In 2.2.3 /usr/include/linux/version.h includes
* a macro for this, but 2.0.35 doesn't - so I add
* it here if necessary. */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif
/* Conditional compilation. LINUX_VERSION_CODE is
* the code (as per KERNEL_VERSION) of this version. */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
#include <asm/uaccess.h> /* for put_user */
#endif
#define SUCCESS 0
/* Device Declarations **************************** */
/* The name for our device, as it will appear
* in /proc/devices */
#define DEVICE_NAME "char_dev"
/* The maximum length of the message from the device */
#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 function is called whenever a process
* attempts to open the device file */
static int device_open(struct inode *inode,
                        struct file *file)
{
    static int counter = 0;
    #ifdef DEBUG
    printk ("device_open(%p,%p)\n", inode, file);
    #endif
    /* This is how you get the minor device number in
    * case you have more than one physical device using
    * the driver. */
    printk("Device: %d.%d\n",
            inode->i_rdev >> 8, inode->i_rdev & 0xFF);
    /* We don't want to talk to two processes at the
    * same time */
    if (Device_Open)
        return -EBUSY;
    /* If this was a process, we would have had to
    * be more careful here.
    *
    *In the case of processes, the danger would be
    *that one process might have check Device_Open
    *and then be replaced by the schedualer by another
    *process which runs this function. Then, when
    *the first process was back on the CPU, it would assume
    *the device is still not open.
    * However, Linux guarantees that a process won't
    * be replaced while it is running in kernel context.
    *
    * In the case of SMP, one CPU might increment
    *Device_Open while another CPU is here, right after the check.
    *However, in version 2.0 of the kernel this is not a problem
    *because there's a lock to guarantee only one CPU will
    *be kernel module at the same time.
    *This is bad in terms of performance, so version 2.2 changed it.
    *Unfortunately, I don't have access to an SMP box
    *to check how it works with SMP.
    */
    Device_Open++;
    /* Initialize the message. */
    sprintf(Message,
    "If I told you once, I told you %d times - %s",
    counter++,
    "Hello, world\n");
    /* The only reason we're allowed to do this sprintf
    * is because the maximum length of the message
    * (assuming 32 bit integers - up to 10 digits
    * with the minus sign) is less than BUF_LEN, which
    * is 80. BE CAREFUL NOT TO OVERFLOW BUFFERS,
    * ESPECIALLY IN THE KERNEL!!!
    */
    Message_Ptr = Message;
    /* Make sure that the module isn't removed while
    * the file is open by incrementing the usage count
    * (the number of opened references to the module, if
    * it's not zero rmmod will fail)
    */
    MOD_INC_USE_COUNT;
    return SUCCESS;
}
/* This function is called when a process closes the
* device file. It doesn't have a return value in
* version 2.0.x because it can't fail (you must ALWAYS
* be able to close a device). In version 2.2.x it is
* allowed to fail - but we won't let it.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static int device_release(struct inode *inode,
                            struct file *file)
#else
static void device_release(struct inode *inode,
                            struct file *file)
#endif
{
    #ifdef DEBUG
    printk ("device_release(%p,%p)\n", inode, file);
    #endif
    /* We're now ready for our next caller */
    Device_Open --;
    /* Decrement the usage count, otherwise once you
    * opened the file you'll never get rid of the module.
    */
    MOD_DEC_USE_COUNT;
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
    return 0;
    #endif
}
/* This function is called whenever a process which
* have already opened the device file attempts to
* read from it. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_read(struct file *file,
                            char *buffer, /* The buffer to fill with data */
                            size_t length, /* The length of the buffer */
                            loff_t *offset) /* Our offset in the file */
#else
static int device_read(struct inode *inode,
                        struct file *file,
                        char *buffer, /* The buffer to fill with
                        * the data */
                        int length) /* The length of the buffer
                        * (mustn't write beyond that!) */
#endif
{
    /* Number of bytes actually written to the buffer */
    int bytes_read = 0;
    /* 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 - unsupported in this example. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_write(struct file *file,
                            const char *buffer, /* The buffer */
                            size_t length, /* The length of the buffer */
                            loff_t *offset) /* Our offset in the file */
#else
static int device_write(struct inode *inode,
                        struct file *file,
                        const char *buffer,
                        int length)
#endif
{
    return -EINVAL;
}
/* Module Declarations ***************************** */
/* The major device number for the device. This is
* global (well, static, which in this context is global
* within this file) because it has to be accessible
* both for registration and for release. */
static int Major;
/* 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 = {
    NULL, /* seek */
    device_read,
    device_write,
    NULL, /* readdir */
    NULL, /* select */
    NULL, /* ioctl */
    NULL, /* mmap */
    device_open,
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
    NULL, /* flush */
    #endif
    device_release /* a.k.a. close */
};
/* Initialize the module - Register the character device */
int init_module()
{
    /* Register the character device (atleast try) */
    Major = module_register_chrdev(0,
                                    DEVICE_NAME,
                                    &Fops);
    /* Negative values signify an error */
    if (Major < 0) {
        printk ("%s device failed with %d\n",
        "Sorry, registering the character",
        Major);
        return Major;
    }
    printk ("%s The major device number is %d.\n",
            "Registeration is a success.",
            Major);
    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 <name> c %d <minor>\n", Major);
    printk ("You can try different minor numbers %s",
            "and see what happens.\n");
    return 0;
}

/* Cleanup - unregister the appropriate file from /proc */
void cleanup_module()
{
    int ret;
    /* Unregister the device */
    ret = module_unregister_chrdev(Major, DEVICE_NAME);
    /* If there's an error, report it */
    if (ret < 0)
        printk("Error in unregister_chrdev: %d\n", ret);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值