您的位置:首页 > 运维架构 > Linux

linux内核模块编程10

2013-04-16 10:40 267 查看
Chapter 10. Replacing Printks
Table of Contents
替换printk
让你的键盘指示灯闪起来
替换printk
在the Section called 使用 X带来的问题 in Chapter 1中, 我说过最好不要在X中进行内核模块编程。在真正的内核模块开发中的确是这样。 但在实际应用中,你想在任何加载模块的tty[1]终端中显示信息。 

实现的方法是使用current指针,一个指向当前运行进程的指针,来获取当前任务的 tty终端的结构体。然后,我们找到在该tty结构体中 用来向tty写入字符信息的函数的指针。通过指针我们使用该函数来向终端写入信息。

Example 10-1. print_string.c

/* 
*  print_string.c - Send output to the tty we're running on, regardless if it's
*  through X11, telnet, etc.  We do this by printing the string to the tty
*  associated with the current task.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>        /* For current */
#include <linux/tty.h>                /* For the tty declarations */
#include <linux/version.h>        /* For LINUX_VERSION_CODE */

MODULE_LICENSE("GPL" );
MODULE_AUTHOR(" Peter Jay Salzman" );

static void print_string(char *str)
{
        struct tty_struct *my_tty;

        /* 
         * tty struct went into signal struct in 2.6.6 
         */
#if ( LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,5) )
        /* 
         * The tty for the current task 
         */
        my_tty = current->tty;
#else
        /* 
         * The tty for the current task, for 2.6.6+ kernels 
         */
        my_tty = current->signal->tty;
#endif

        /* 
         * If my_tty is NULL, the current task has no tty you can print to 
         * (ie, if it's a daemon).  If so, there's nothing we can do.
         */
        if (my_tty != NULL) {

                /* 
                 * my_tty->driver is a struct which holds the tty's functions,
                 * one of which (write) is used to write strings to the tty. 
                 * It can be used to take a string either from the user's or 
                 * kernel's memory segment.
                 *
                 * The function's 1st parameter is the tty to write to,
                 * because the same function would normally be used for all 
                 * tty's of a certain type.  The 2nd parameter controls whether
                 * the function receives a string from kernel memory (false, 0)
                 * or from user memory (true, non zero).  The 3rd parameter is
                 * a pointer to a string.  The 4th parameter is the length of
                 * the string.
                 */
                ((my_tty->driver)->write) (my_tty,        /* The tty itself */
                                           0,        /* Don't take the string 
                                                   from user space        */
                                           str,        /* String                 */
                                           strlen(str));        /* Length */

                /* 
                 * ttys were originally hardware devices, which (usually) 
                 * strictly followed the ASCII standard.  In ASCII, to move to
                 * a new line you need two characters, a carriage return and a
                 * line feed.  On Unix, the ASCII line feed is used for both 
                 * purposes - so we can't just use \n, because it wouldn't have
                 * a carriage return and the next line will start at the
                 * column right after the line feed.
                 *
                 * This is why text files are different between Unix and 
                 * MS Windows.  In CP/M and derivatives, like MS-DOS and 
                 * MS Windows, the ASCII standard was strictly adhered to,
                 * and therefore a newline requirs both a LF and a CR.
                 */
                ((my_tty->driver)->write) (my_tty, 0, "\015\012", 2);
        }
}

static int __init print_string_init(void)
{
        print_string("The module has been inserted.  Hello world!" );
        return 0;
}

static void __exit print_string_exit(void)
{
        print_string("The module has been removed.  Farewell world!" );
}

module_init(print_string_init);
module_exit(print_string_exit);
Notes
[1] Teletype, 原先是一种用来和Unix系统交互的键盘和打印机结合起来的装置。现在,它只是一个用来同Unix或类似的系统交流文字流 的抽象的设备,而不管它具体是显示器,X中的xterm,还是一个通过telnet的网络连接。
让你的键盘指示灯闪起来
你也许想让你的模块更直接的同外界交流,你的键盘指示灯就是一个不错的选择。它可以及时显示模块的工作状态, 吸引你的注意,并且它们不许要任何设置,使用起来也不像向终端或磁盘写入信息那么危险。 

下面的这个模块代码演示了一个相当小的模块:当被加载入内核时,键盘指示灯就不停的闪烁,直到它被卸载。 

Example 10-2. kbleds.c

/* 
*  kbleds.c - Blink keyboard leds until the module is unloaded.
*/

#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/tty.h>                /* For fg_console, MAX_NR_CONSOLES */
#include <linux/kd.h>                /* For KDSETLED */
#include <linux/console_struct.h>        /* For vc_cons */

MODULE_DESCRIPTION("Example module illustrating the use of Keyboard LEDs.");
MODULE_AUTHOR("Daniele Paolo Scarpazza");
MODULE_LICENSE("GPL");

struct timer_list my_timer;
struct tty_driver *my_driver;
char kbledstatus = 0;

#define BLINK_DELAY   HZ/5
#define ALL_LEDS_ON   0x07
#define RESTORE_LEDS  0xFF

/*
* Function my_timer_func blinks the keyboard LEDs periodically by invoking
* command KDSETLED of ioctl() on the keyboard driver. To learn more on virtual 
* terminal ioctl operations, please see file:
*     /usr/src/linux/drivers/char/vt_ioctl.c, function vt_ioctl().
*
* The argument to KDSETLED is alternatively set to 7 (thus causing the led 
* mode to be set to LED_SHOW_IOCTL, and all the leds are lit) and to 0xFF
* (any value above 7 switches back the led mode to LED_SHOW_FLAGS, thus
* the LEDs reflect the actual keyboard status).  To learn more on this, 
* please see file:
*     /usr/src/linux/drivers/char/keyboard.c, function setledstate().

*/

static void my_timer_func(unsigned long ptr)
{
        int *pstatus = (int *)ptr;

        if (*pstatus == ALL_LEDS_ON)
                *pstatus = RESTORE_LEDS;
        else
                *pstatus = ALL_LEDS_ON;

        (my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
                            *pstatus);

        my_timer.expires = jiffies + BLINK_DELAY;
        add_timer(&my_timer);
}

static int __init kbleds_init(void)
{
        int i;

        printk(KERN_INFO "kbleds: loading\n");
        printk(KERN_INFO "kbleds: fgconsole is %x\n", fg_console);
        for (i = 0; i < MAX_NR_CONSOLES; i++) {
                if (!vc_cons[i].d)
                        break;
                printk(KERN_INFO "poet_atkm: console[%i/%i] #%i, tty %lx\n", i,
                       MAX_NR_CONSOLES, vc_cons[i].d->vc_num,
                       (unsigned long)vc_cons[i].d->vc_tty);
        }
        printk(KERN_INFO "kbleds: finished scanning consoles\n");

        my_driver = vc_cons[fg_console].d->vc_tty->driver;
        printk(KERN_INFO "kbleds: tty driver magic %x\n", my_driver->magic);

        /*
         * Set up the LED blink timer the first time
         */
        init_timer(&my_timer);
        my_timer.function = my_timer_func;
        my_timer.data = (unsigned long)&kbledstatus;
        my_timer.expires = jiffies + BLINK_DELAY;
        add_timer(&my_timer);

        return 0;
}

static void __exit kbleds_cleanup(void)
{
        printk(KERN_INFO "kbleds: unloading...\n");
        del_timer(&my_timer);
        (my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
                            RESTORE_LEDS);
}

module_init(kbleds_init);
module_exit(kbleds_cleanup);
如果上面的方法都无法满足你调试的需要,你就可能需要其它的技巧了。还记得那个在 make menuconfig 时的CONFIG_LL_DEBUG参数吗?如果你激活该选项,你就可以获得对串口的底层操纵。如果这仍然不够爽,你还可以对 kernel/printk.c或其它的基本的系统底层调用打补丁来使用printascii,从而可以通过串口跟踪
内核的每步动作。如果你的架构不支持上面的例子却有一个标准的串口,这可能应该是你首先应该考虑的了。通过网络上的 终端调试同样值得尝试。 

尽管有很多关于如何调试的技巧,但我要提醒的是任何调试都会代码带来影响。加入调试代码足以导致原始代码产生bug的 条件的消失,所以尽可能少的加入调试代码并且确保它们不出现在成熟的代码中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: