发布新日志

  • 关于实现udev/mdev自动挂载与卸载

    2013-03-12 16:12:18

    在网上有很多关于讲mdev的自动挂载基本上都是一个版本,经过测试自动挂载确实可行,但是关于自动卸载mdev似乎不能很好的支持,经过修改已经可以做到与udev的效果相似。不能在挂载的目录中进行热插拔,否则会出现问题,不过此问题在下次插入U盘时不会造成影响,可能对U盘有损坏。

    本文介绍了mdev与udev两种方法来实现自动挂载,读者可根据需要任选其一即可。

            首先介绍一下mdev与udev之间的关系:

            mdev是busybox中的一个udev管理程序的一个精简版,他也可以实现设备节点的自动创建和设备的自动挂载,只是在实现的过程中有点差异,在发生热插拔时间的时候,mdev是被hotplug直接调用,这时mdev通过环境变量中的 ACTION 和 DEVPATH,来确定此次热插拔事件的动作以及影响了/sys中的那个目录。接着会看看这个目录中是否有“dev”的属性文件,如果有就利用这些信息为这个设备在/dev 下创建设备节点文件。

     

     

    /***********************************************************************************************************************************************************************************/

    1.mdev支持

    ①用busybox制作根文件系统的时候,要选择支持mdev机制

    Linux System Utilities  --->   
               [*] mdev      
               [*]   Support /etc/mdev.conf
               [*]     Support command execution at device addition/removal

    ②在文件系统/etc/init.d/rsC文件中添加如下内容

    Vi  /etc/init.d/rcS
            mount -t tmpfs mdev /dev
            mount -t sysfs sysfs /sys
            mkdir /dev/pts
            mount -t devpts devpts /dev/pts

            echo /sbin/mdev>/proc/sys/kernel/hotplug
            mdev –s

    这些语句的添加在busybox的/doc/mdev.txt中可以找到。

     

    ③添加对热插拔事件的响应,实现U盘和SD卡的自动挂载。

    Vi /etc/mdev.conf
           sd[a-z][0-9]      0:0 666        @/etc/mdev/udisk_insert                        

             sd[a-z]                   0:0 666          $/etc/mdev/udisk_remove

    红色部分,是一个脚本,脚本内容可以根据我们的需要定制,可以实现挂载,卸载或其他一些功能。

    注:@表示是在插入(创建设备结点)后执行后面的脚本,$表示在拔出(删除设备结点)前执行后面的脚本。

    如下是自动挂载和卸载的脚本名称及其内容:

    #!/bin/sh

    if [ -d /sys/block/*/$MDEV ]  ; then

      mkdir -p /media/$MDEV

      mount /dev/$MDEV /media/$MDEV 

    fi

    根文件系统中的etc/mdev/udisk_remove文件内容:
            #!/bin/sh
            umount -l /media/$MDEV
            rm -rf /media/$MDEV
     
            #!/bin/sh
            umount -l /media/sd*
            rm -rf /media/sd*

    修改为红色部分后能够自动挂载

    以上两个脚本需要可执行权限:chmod +x  /etc/mdev/udisk_insert

                   chmod +x etc/mdev/udisk_remove

    /***********************************************************************************************************************************************************************************/

    2.udev支持 

            linux传统上使用静态设备创建的方法,在dev下创建了大量的节点,而不管这些节点相应的硬件设备是否存在。采用udev的方法,系统检测到设备才会去创建这些设备对应的节点。

            这里我们简单的说一下udev的工作原理:

            udev是依赖于sysfs的,当系统中添加一个新的设备后,内核检测到后就会产生一个hotplug event并查找/proc/sys/kernel/hotplug去找出管理设备连接的用户空间程序,若udev已经启动,内核会通知udev去检测sysfs中关于这个新设备的信息并创建设备节点。如/dev/vcs,在/sys/class/tty/vcs/dev存放的是”7:0”,既/dev/vcs的主次设备号。并且udev还会根据/etc/udev/rules.d中的规则文件实现一些相应的功能。

    下面我们介绍一下如何实现设备节点的自动创建及u盘或sd卡的自动挂载。

    因为文件系统中默认是没有对udev进行支持的,所以我们移植一个udev。

    1.下载udev源码udev-100.tar.bz2,并解压

    网址:http://www.us.kernel.org/pub/linux/utils/kernel/hotplug

    2.交叉编译。

    修改makefile,具体修改如下:

            cross = arm-linux-

    保存退出。

    然后执行命令:make 进行编译,然后执行arm-linux-strip udev udevd udevstart udevinfo udevtest,并拷贝这些文件到目标板根文件/bin目录下面。

    3.添加udev的支持

    下面三种方法功能相同

            (1)并修改etc/init.d/rcs脚本,然后添加如下命令:

            /bin/mount -t sysfs sysfs /sys 

            /bin/mount -t tmpfs tmpfs /dev

            /bin/udevd --daemon

            /bin/udevstart

            (2)如果linuxrc是二进制文件的话 

            rm /linuxrc

            vi /linuxrc

            添加如下内容

            /bin/mount -t sysfs sysfs /sys

            /bin/mount -t tmpfs tmpfs /dev

            /bin/udevd --daemon

            /bin/udevstart

            exec /sbin/init

            (3)修改/etc/fstab为

            #device mount-point type options dump fsck order

            proc /proc proc defaults 0 0

            tmpfs /tmp tmpfs defaults 0 0

            sysfs /sys sysfs defaults 0 0

            tmpfs /dev tmpfs defaults 0 0

            修改/etc/init.d/rcs,添加如下内容

    /bin/udevd --daemon

    /bin/udevstart

            重新启动系统,文件系统就能够自动创建节点。

     

    4.在/etc下创建udev目录

    5.在/etc/udev下穿件目录rules.d和文件udev.conf

    6.在udev.conf中添加如下内容

    # udev.conf

            # the initial syslog(3) priority: "err", "info", "debug" or its

            # numerical equivalent. for runtime debugging, the daemons internal

            # state can be changed with: "udevcontrol log_priority=<value>".

            udev_log="err"

    7.在rules.d下创建规则文件

    如实现u盘自动挂载

            vim 11-add-usb.rules

    添加如下内容

            action!="add",goto="farsight"

            kernel=="sd[a-z][0-9]",run+="/sbin/mount-usb.sh %k"

            label="farsight"

    这个文件中action后是说明是什么事件,kernel后是说明是什么设备比如sda1,mmcblk0p1等,run这个设备插入后去执行哪个程序%k是传入这个程序的参数,这里%k=kernel的值也就是sda1等http://www.woaidiannao.com。

    在/sbin/下创建mount-usb.sh文件添加如下内容 计算机

            #!/bin/sh

            /bin/mount -t vfat /dev/$1 /tmp

            sync

    修改文件权限为其添加可执行的权限。

    这样就实现了u盘的自动挂载,下面附上u盘的卸载规则文件和sd卡的文件

    usb卸载

    11-add-remove.rules

            action !="remove",goto="farsight"

            subsystem!="block",goto="farsight"

            kernel=="sd[a-z][0-9]",run+="/sbin/umount-usb.sh"

            label="farsight"

    umount-usb.sh

            #!/bin/sh

            sync

            umount /tmp/

    sd卡挂载

    12-add-sd.rules

    action!="add",goto="farsight"

            kernel=="mmcblk[0-9]p[0-9]",run+="/sbin/mount-sd.sh %k"

            label="farsight"

    mount-sd.sh

            #!/bin/sh

            /bin/mount -t vfat /dev/$1 /tmp

            sync

     

    sd卸载

    12-remove-sd.rules

            action !="remove",goto="farsight"

            subsystem!="block",goto="farsight"

            kernel=="mmcblk*",run+="/sbin/umount-sd.sh"

            label="farsight"

    umount-sd.sh

            #!/bin/sh

            sync

            /bin/umount /tmp/

  • busybox mdev使用 自动创建设备节点

    2013-03-12 15:42:40

    quote]------本文很多关于mdev解释的内容来源于网络,有说的不对的地方,望大家指正。-------
    同时,各位也可以在我的blog里面找到,http://blog.chinaunix.net/space.php?uid=20752341&do=blog&id=3081976

       写Linux 设备驱动程序的时候,很多时候都是利用mknod 命令手动创建设备节点,mdev可以用来在模块加载-- insmod-- 的时候自动在/dev 目录下创建相应设备节点,并在卸载模块-- rmmod --时删除该节点。
       内核同时提供了class_create( …) 函数,可以用它来创建一个类,这个类存放于sysfs 下面,一旦创建好了这个类,再调用device_create(…) 函数来在/dev 目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev 会自动响应device_create( …) 函数,去/sysfs 下寻找对应的类从而创建设备节点。
    举例如下(只是把关键的函数说明下):
    1. #include <linux/module.h>
    2. #include <linux/fs.h>
    3. #include <linux/string.h>
    4. #include <linux/init.h>
    5. #include <linux/platform_device.h>
    6. #include <linux/interrupt.h>
    7. #include <linux/rtc.h>
    8. #include <linux/bcd.h>
    9. #include <linux/clk.h>
    10. #include <linux/device.h>

    11. MODULE_LICENSE("Dual BSD/GPL");

    12. static struct class *firstdrv_class;
    13. static struct class_device *firstdrv_class_dev;


    14. static int first_drv_open(struct inode *inode, struct file *file)
    15. {
    16.     printk("first_drv_open\n");
    17.     
    18.     return 0;
    19. }

    20. static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
    21. {
    22.     printk("first_drv_write\n");
    23.     
    24.     return 0;
    25. }

    26. static struct file_operations first_drv_fops =
    27. {
    28.     .owner = THIS_MODULE,
    29.     .open = first_drv_open,
    30.     .write = first_drv_write,
    31. };

    32. int major;
    33. static int first_drv_init(void)
    34. {
    35.     printk("Hello world!\n");
    36.     major = register_chrdev(0, "first_drv", &first_drv_fops);
    37.     firstdrv_class = class_create(THIS_MODULE, "firstdrv");
    38.     firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");
    39.     return 0;
    40. }

    41. static void first_drv_exit(void)
    42. {
    43.     printk("Bye, hello world!\n");
    44.     unregister_chrdev(major, "first_drv");
    45.     class_device_unregister(firstdrv_class_dev);
    46.     class_destroy(firstdrv_class);
    47. }

    48. module_init(first_drv_init);
    49. module_exit(first_drv_exit);
    复制代码
    附上Makefile 文件:
    1. KERNELDIR = /opt/linux-2.6.22.6/
    2.     # The current directory is passed to sub-makes as argument
    3. PWD := $(shell pwd)
    4. CC =arm-linux-gcc
    5. obj-m := hello.o
    6. modules:
    7.     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    8. clean:
    9.     rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
    10. .PHONY: modules modules_install clean
    复制代码

    进入目录,make


    生成 hello.ko 文件,拷贝到nfs目录,发现insmod 成功,但是没有自动创建 /dev/xyz设备。

    1. # insmod hello.ko
    2. Hello world!
    3. # lsmod
    4. hello 2188 0 - Live 0xbf000000
    5. # cat /proc/devices
    6. Character devices:
    7.   1 mem
    8.   2 pty
    9.   3 ttyp
    10.   4 /dev/vc/0
    11.   4 tty
    12.   4 ttyS
    13.   5 /dev/tty
    14.   5 /dev/console
    15.   5 /dev/ptmx
    16.   6 lp
    17.   7 vcs
    18. 10 misc
    19. 13 input
    20. 29 fb
    21. 90 mtd
    22. 99 ppdev
    23. 128 ptm
    24. 136 pts
    25. 180 usb
    26. 189 usb_device
    27. 204 s3c2410_serial
    28. 252 first_drv
    29. 253 usb_endpoint
    30. 254 rtc

    31. Block devices:
    32.   1 ramdisk
    33.   7 loop
    34.   8 sd
    35. 31 mtdblock
    36. 65 sd
    37. 66 sd
    38. 67 sd
    39. 68 sd
    40. 69 sd
    41. 70 sd
    42. 71 sd
    43. 128 sd
    44. 129 sd
    45. 130 sd
    46. 131 sd
    47. 132 sd
    48. 133 sd
    49. 134 sd
    50. 135 sd
    51. # ls /dev/xyz
    52. ls: /dev/xyz: No such file or directory
    复制代码


    查看busybox里面的 mdev.txt 文件,于是在 /etc/init.d/rcS里面增加如下命令,蓝色部分(个人觉得 mount -t proc proc /proc 这条可以不用):
    #!/bin/sh
    ifconfig eth0 192.168.1.133
    ifconfig lo up
    mount -a
    mount -t proc proc /proc
    mount -t sysfs sysfs /sys
    echo /sbin/mdev > /proc/sys/kernel/hotplug
    mdev -s

    然后
    reboot,重新insmod 相应的.ko文件

    1. # cd /mnt/
    2. # insmod hello.ko
    3. Hello
    4. # lsmod
    5. hello 2188 0 - Live 0xbf000000
    6. # cat /proc/devices
    7. Character devices:
    8.   1 mem
    9.   2 pty
    10.   3 ttyp
    11.   4 /dev/vc/0
    12.   4 tty
    13.   4 ttyS
    14.   5 /dev/tty
    15.   5 /dev/console
    16.   5 /dev/ptmx
    17.   6 lp
    18.   7 vcs
    19. 10 misc
    20. 13 input
    21. 29 fb
    22. 90 mtd
    23. 99 ppdev
    24. 128 ptm
    25. 136 pts
    26. 180 usb
    27. 189 usb_device
    28. 204 s3c2410_serial
    29. 252 first_drv
    30. 253 usb_endpoint
    31. 254 rtc

    32. Block devices:
    33.   1 ramdisk
    34.   7 loop
    35.   8 sd
    36. 31 mtdblock
    37. 65 sd
    38. 66 sd
    39. 67 sd
    40. 68 sd
    41. 69 sd
    42. 70 sd
    43. 71 sd
    44. 128 sd
    45. 129 sd
    46. 130 sd
    47. 131 sd
    48. 132 sd
    49. 133 sd
    50. 134 sd
    51. 135 sd
    52. # ls /dev/xyz
    53. /dev/xyz
    复制代码

    注意上面的蓝色字体部分,自动创建成功。


    echo /sbin/mdev >/proc/sys/kernel/hotplug 当有热插拔事件产生时,内核就会调用位于/sbin目录的mdev。这时mdev通过环境变量中的 ACTION DEVPATH,(这两个变量是系统自带的)来确定此次热插拔事件的动作以及影响了/sys中的那个目录。接着会看看这个目录中是否有“dev”的属性文件,如果有就利用这些信息为这个设备在/dev 下创建设备节点文件。



    驱动里,那2个函数只是在sysfs里建信息;需要hotplug的mdev根据这些信息来创建节点

  • Linux内核驱动自动创建设备节点文件

    2013-03-12 15:26:55

    分类: LINUX

    Linux下生成驱动设备节点文件的方法有3个:1、手动mknod;2、利用devfs;3、利用udev
    在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点。
    在2.6.17以前,在/dev目录下生成设备文件很容易,
    devfs_mk_bdev
    devfs_mk_cdev
    devfs_mk_symlink
    devfs_mk_dir
    devfs_remove
    这几个是纯devfs的api,2.6.17以前可用,但后来devfs被sysfs+udev的形式取代,同时期sysfs文件系统可以用的api:
    class_device_create_file,在2.6.26以后也不行了,现在,使用的是device_create ,从2.6.18开始可用
    struct device *device_create(struct class *class, struct device *parent,
    dev_t devt, const char *fmt, ...)
    从2.6.26起又多了一个参数drvdata: the data to be added to the device for callbacks
    不会用可以给此参数赋NULL
    struct device *device_create(struct class *class, struct device *parent,
    dev_t devt, void *drvdata, const char *fmt, ...)
     
    下面着重讲解第三种方法udev
    在驱动用加入对udev的支持主要做的就是:在驱动初始化的代码里调用class_create(...)为该设备创建一个class,再为每个设备调用device_create(...)( 在2.6较早的内核中用class_device_create)创建对应的设备。
    内核中定义的struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
    struct class和class_create(…) 以及device_create(…)都包含在在/include/linux/device.h中,使用的时候一定要包含这个头文件,否则编译器会报错。
    struct class定义在头文件include/linux/device.h中
    class_create(…)在/drivers/base/class.c中实现
    device_create(…)函数在/drivers/base/core.c中实现
    class_destroy(...),device_destroy(...)也在/drivers/base/core.c中实现调用过程类似如下:
    static struct class *spidev_class;
     
    /*-------------------------------------------------------------------------*/
     
    static int __devinit spidev_probe(struct spi_device *spi)
    {
            ....
            
            dev = device_create(spidev_class, &spi->dev, spidev->devt,
                    spidev, "spidev%d.%d",
                    spi->master->bus_num, spi->chip_select);
            ...
    }
     
    static int __devexit spidev_remove(struct spi_device *spi)
    {
        ......
        device_destroy(spidev_class, spidev->devt);
        .....
     
        return 0;
    }
     
    static struct spi_driver spidev_spi = {
        .driver = {
            .name =        "spidev",
            .owner =    THIS_MODULE,
        },
        .probe =    spidev_probe,
        .remove =    __devexit_p(spidev_remove),
     
    };
     
    /*-------------------------------------------------------------------------*/
     
    static int __init spidev_init(void)
    {
        ....
     
        spidev_class = class_create(THIS_MODULE, "spidev");
        if (IS_ERR(spidev_class)) {
            unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
            return PTR_ERR(spidev_class);
        }
        ....
    }
    module_init(spidev_init);
     
    static void __exit spidev_exit(void)
    {
        ......
        class_destroy(spidev_class);
        ......
    }
    module_exit(spidev_exit);
     
    MODULE_DESCRIPTION("User mode SPI device interface");
    MODULE_LICENSE("GPL");
     
     下面以一个简单字符设备驱动来展示如何使用这几个函数 
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
     
    int HELLO_MAJOR = 0;
    int HELLO_MINOR = 0;
    int NUMBER_OF_DEVICES = 2;
     
    struct class *my_class;
    //struct cdev cdev;
    //dev_t devno;
     
    struct hello_dev {
    struct device *dev;
    dev_t chrdev;
    struct cdev cdev;
    };

    static struct hello_dev *my_hello_dev = NULL;

    struct file_operations hello_fops = {
     .owner = THIS_MODULE
    };
     
    static int __init hello_init (void)
    {
    int err = 0;
    struct device *dev;

    my_hello_dev = kzalloc(sizeof(struct hello_dev), GFP_KERNEL);
    if (NULL == my_hello_dev) {
    printk("%s kzalloc failed!\n",__func__);
    return -ENOMEM;
    }

    devno = MKDEV(HELLO_MAJOR, HELLO_MINOR);
    if (HELLO_MAJOR)
    err= register_chrdev_region(my_hello_dev->chrdev, 2, "memdev");
    else
    {
    err = alloc_chrdev_region(&my_hello_dev->chrdev, 0, 2, "memdev");
    HELLO_MAJOR = MAJOR(devno);
    }  
    if (err) {
    printk("%s alloc_chrdev_region failed!\n",__func__);
    goto alloc_chrdev_err;
    }
    printk("MAJOR IS %d\n",HELLO_MAJOR);

    cdev_init(&(my_hello_dev->cdev), &hello_fops);
    my_hello_dev->cdev.owner = THIS_MODULE;
    err = cdev_add(&(my_hello_dev->cdev), my_hello_dev->chrdev, 1);
    if (err) {
    printk("%s cdev_add failed!\n",__func__);
    goto cdev_add_err;
    }
    printk (KERN_INFO "Character driver Registered\n");

    my_class = class_create(THIS_MODULE,"hello_char_class");  //类名为hello_char_class
    if(IS_ERR(my_class)) 
    {
    err = PTR_ERR(my_class);
    printk("%s class_create failed!\n",__func__);
    goto class_err;
    }

    dev = device_create(my_class,NULL,my_hello_dev->chrdev,NULL,"memdev%d",0);      //设备名为memdev
    if (IS_ERR(dev)) {
    err = PTR_ERR(dev);
    gyro_err("%s device_create failed!\n",__func__);
    goto device_err;
    }

    printk("hello module initialization\n");
    return 0;
     
    device_err:
    device_destroy(my_class, my_hello_dev->chrdev);
    class_err:
    cdev_del(my_hello_dev->chrdev);
    cdev_add_err:
    unregister_chrdev_region(my_hello_dev->chrdev, 1);
    alloc_chrdev_err:
    kfree(my_hello_dev);
    return err;
    }
     
    static void __exit hello_exit (void)
    {
    cdev_del (&(my_hello_dev->cdev));
    unregister_chrdev_region (my_hello_dev->chrdev,1);
    device_destroy(my_class, devno);         //delete device node under /dev//必须先删除设备,再删除class类
    class_destroy(my_class);                 //delete class created by us
    printk (KERN_INFO "char driver cleaned up\n");
    }
     
    module_init (hello_init);
    module_exit (hello_exit);
     
    MODULE_LICENSE ("GPL");
    这样,模块加载后,就能在/dev目录下找到memdev这个设备节点了。
     例2:内核中的drivers/i2c/i2c-dev.c
    在i2cdev_attach_adapter中调用device_create(i2c_dev_class, &adap->dev,
             MKDEV(I2C_MAJOR, adap->nr), NULL,
             "i2c-%d", adap->nr);
    这样在dev目录就产生i2c-0  或i2c-1节点
     
    接下来就是udev应用,udev是应用层的东西,udev需要内核sysfs和tmpfs的支持,sysfs为udev提供设备入口和uevent通道,tmpfs为udev设备文件提供存放空间
    udev的源码可以在去相关网站下载,然后就是对其在运行环境下的移植,指定交叉编译环境,修改Makefile下的CROSS_COMPILE,如为mipsel-linux-,DESTDIR=xxx,或直接make CROSS_COMPILE=mipsel-linux-,DESTDIR=xxx 并install
    把主要生成的udevd、udevstart拷贝rootfs下的/sbin/目录内,udev的配置文件udev.conf和rules.d下的rules文件拷贝到rootfs下的/etc/目录内
    并在rootfs/etc/init.d/rcS中添加以下几行:
    echo “Starting udevd...”
    /sbin/udevd --daemon
    /sbin/udevstart
    (原rcS内容如下:
    # mount filesystems
    /bin/mount -t proc /proc /proc
    /bin/mount -t sysfs sysfs /sys
    /bin/mount -t tmpfs tmpfs /dev
    # create necessary devices
    /bin/mknod /dev/null c 1 3
    /bin/mkdir /dev/pts
    /bin/mount -t devpts devpts /dev/pts
    /bin/mknod /dev/audio c 14 4
    /bin/mknod /dev/ts c 10 16
    这样当系统启动后,udevd和udevstart就会解析配置文件,并自动在/dev下创建设备节点文件
     
  • Linux字符驱动中动态分配设备号与动态生成设备节点

    2013-03-12 14:53:13

    在驱动程序中初始化入口函数中,向内核注册一个设备后,往往要注册一个类

    例如
    static int __init mydriver_init(void) //驱动程序的初始化
    {  
       ……
        MYDRIVER_Major = register_chrdev(0, DEVICE_NAME, &mydriver_fops); //向内核注册一个设备,返回值为注册的主设备号
        if (MYDRIVER_Major < 0)
        {
            printk(DEVICE_NAME " can't register major number\n");
            return MYDRIVER_Major;
        }
        ……
        mydriver_class = class_create(THIS_MODULE, DEVICE_NAME); 
    //注册一个类,使mdev可以在"/dev/"目录下 面建立设备节点
        ……
        //创建一个设备节点,节点名为DEVICE_NAME
        device_create(mydriver_class, NULL, MKDEV(MYDRIVER_Major, 0), NULL, DEVICE_NAME);
        ……
    }

    从linux内核2.6的某个版本之后,devfs不复存在,udev成为devfs的 替代。相比devfs,udev有很多优势,在此就不罗嗦了,提醒一点,udev是应用层的东东,不要试图在内核的配置选项里找到它;加入对udev的支 持很简单,以作者所写的一个字符设备驱动为例,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用 class_device_create创建对应的设备。大致用法如下:
    struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);
    class_device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);

    device_create()  replaces  class_device_create()  in 2.6.21
    这样的module被加载时,udev daemon就会自动在/dev下创建my_device设备文件

    Linux字符驱动中动态分配设备号与动态生成设备节点
    http://www.cnblogs.com/zhuyp1015/archive/2012/05/22/2514008.html

    class_create(),class_device_create()或device_create()自动创建设备文件结点
    http://yuxu9710108.blog.163.com/blog/static/237515342011612104030470/

    Linux高级字符设备驱动
    http://www.linuxidc.com/Linux/2012-05/60469.htm


  • Android PMEM驱动研究(一)

    2013-03-05 08:51:21

    PMEM并不像Ashmem和binder那样,选中就可以被Android系统使用,他是一个platform设备,需要注册才可以使用.
      下面以S3C6410为例,描述使用流程:
      1)选中内核选项
      Device Drivers --->
       Misc devices --->
          Android pmem allocator
      2)修改你的dev.c注册文件,添加如下内容:

    java代码:
    1. #ifdef CONFIG_ANDROID_PMEM
    2. static struct android_pmem_platform_data android_pmem_pdata = {
    3. .name = "pmem",
    4. .start = PMEM_BASE,
    5. .size = PMEM_BASE_SIZE,
    6. .no_allocator = 1,
    7. .cached = 1,
    8. };

    9. static struct android_pmem_platform_data android_pmem_adsp_pdata = {
    10. .name = "pmem_adsp",
    11. .start = PMEM_ADSP_BASE,
    12. .size = PMEM_ADSP_BASE_SIZE,
    13. .no_allocator = 0,
    14. .cached = 0,
    15. };

    16. struct platform_device android_pmem_device = {
    17. .name = "android_pmem",
    18. .id = 0,
    19. .dev = { .platform_data = &android_pmem_pdata },
    20. };

    21. struct platform_device android_pmem_adsp_device = {
    22. .name = "android_pmem",
    23. .id = 1,
    24. .dev = { .platform_data = &android_pmem_adsp_pdata },
    25. };
    26. #endif
    复制代码
       3)在驱动注册列表中添加如下内容:

    java代码:
    1. static struct platform_device *smdk6410_devices[] __initdata = {
    2. #ifdef CONFIG_ANDROID_PMEM
    3. &android_pmem_device,
    4. &android_pmem_adsp_device,
    5. #endif
    6. };
    复制代码
    4)分配物理地址我用了128MB的最后8MB
      #define PMEM_BASE 0x57900000
      #define PMEM_BASE_SIZE SZ_1M*4
      #define PMEM_ADSP_BASE 0x57c00000
      #define PMEM_ADSP_BASE_SIZE SZ_1M*4
      5)重新编译内核
      6)修改bootargs 减少Linux可管理的MEM
      MEM=120MB
      7)重新启动系统
      启动信息:
      pmem: 1 init
      pmem_adsp: 0 init
      8)查看dev目录,多了pmem和pmem_adsp
  • Linux下linux/arch/arm/plat-s3c24xx/gpio.c的理解

    2013-03-04 18:56:05

     午饭过后,毛毛摸着小肚子又和大家见面了,我们大家一起来把上一篇文章没有学习完的知识学习完。如果毛毛讲的好,今天就奖励陶毛毛看一集《大耳朵图图》。

      好的,我们现在开始学习吧!今天早上在我们的上一篇文章“Linux下arch/arm/mach-s3c2410/include/mach/gpio-nrs.h的理解”里面介绍了一系列的宏,但是我们不知道,为什么Linux内核要这样做?今天下午毛毛就带领大家一起来看看linux/arch/arm/plat-s3c24xx/gpio.c里面的应用。

      毛毛通过查询内核代码,发现linux/arch/arm/plat-s3c24xx/gpio.c,主要是定义了一些Linux下对I/O口的标准配置和读写。包括以下函数:

    void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function);

    unsigned int s3c2410_gpio_getcfg(unsigned int pin);

    void s3c2410_gpio_pullup(unsigned int pin, unsigned int to);

    int s3c2410_gpio_getpull(unsigned int pin);

    void s3c2410_gpio_setpin(unsigned int pin, unsigned int to);

    unsigned int s3c2410_gpio_getpin(unsigned int pin);

    unsigned int s3c2410_modify_misccr(unsigned int clear, unsigned int change);

    int s3c2410_gpio_getirq(unsigned int pin);

    上面函数的作用毛毛就不用再一一解释了,各位同学打开gpio.c文件就会理解,因为这些函数就是对I/O口的输入输出方向,数据读写和配置的函数。

    接下来毛毛仅仅从void s3c2410_gpio_setpin(unsigned int pin, unsigned int to);这个函数来讨论上一篇文章中宏定义的作用。

    首先,我们还是先将这个函数的源程序列出来给大家

    void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
    {
     void __iomem *base = S3C24XX_GPIO_BASE(pin);  //宏计算,得到pin的基地址
     unsigned long ffs = S3C2410_GPIO_OFFSET(pin);      //宏计算,得到pin的相对基地址的偏移地址
     unsigned long flags;
     unsigned long dat;

     local_irq_save(flags);                  //类似于进入临界区                 

     dat = __raw_readl(base + 0x04);           //读I/O口数据
     dat &= ~(1 << offs);
     dat |= to << offs;
     __raw_writel(dat, base + 0x04);            //写I/O口数据

     local_irq_restore(flags);                //类似于退出临界区
    }

    从以上程序大家可能还不能看出什么来,但是毛毛就是那么“打破砂锅问到底”,毛毛进入S3C24XX_GPIO_BASE(pin),S3C2410_GPIO_OFFSET(pin)这两个宏。

    这两个宏定义在arch/arm/mach-s3c2410/include/mach/regs-gpio.h这个文件里面:

    #define S3C2410_GPIO_BASE(pin)   ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
    #define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)

    (S3C24XX_VA_GPIO,是一个宏,代表I/O口的虚拟基地址;这个地方不再多讲,1:我自己对虚拟地址和实际物理地址还不是太熟悉,不能误人子弟。2:如果要讲需要说的东西太多,不容易一下讲清楚,还请各位同学自己琢磨)

    好的,到现在为止我们已经将基本上所有的东西列出来来了,接下来毛毛会用一个例子来将上面讲到的东西联系起来。

    首先我们看下面这句C程序:s3c2410_gpio_setpin(S3C2410_GPA(0), 1);

    这句程序的意思就是将S3C2410端口A的0口置1,相信大家和毛毛一样都懂的。

    S3C2410_GPA(0)是Linux下arch/arm/mach-s3c2410/include/mach/gpio-nrs.h里面的宏定义,进入函数void s3c2410_gpio_setpin(unsigned int pin, unsigned int to);毛毛发现S3C2410_GPA(0)这个参数是作为宏S3C2410_GPIO_BASE(pin)和S3C2410_GPIO_OFFSET(pin)的参数。

    至此我们应该比较明了了吧,今天上午我们对arch/arm/mach-s3c2410/include/mach/gpio-nrs.h文件里面所做的工作就是用来供这两个宏使用的。

    但是为什么要这样定义呢?

    细心的毛毛在arch/arm/mach-s3c2410/include/mach/regs-gpio.h(也就是定义基地址和偏移地址的宏所在的文件)文件里发现一下程序:

    #define S3C2410_GPA0_ADDR0   (1<<0)

    #define S3C2410_GPA1_ADDR16  (1<<1)

    #define S3C2410_GPA2_ADDR17  (1<<2)

    #define S3C2410_GPA3_ADDR18  (1<<3)

    #define S3C2410_GPA4_ADDR19  (1<<4)

    #define S3C2410_GPA5_ADDR20  (1<<5)

    #define S3C2410_GPA6_ADDR21  (1<<6)

    #define S3C2410_GPA7_ADDR22  (1<<7)

    #define S3C2410_GPA8_ADDR23  (1<<8)

    #define S3C2410_GPA9_ADDR24  (1<<9)

    #define S3C2410_GPA10_ADDR25 (1<<10)
    #define S3C2400_GPA10_SCKE   (1<<10)

    #define S3C2410_GPA11_ADDR26 (1<<11)
    #define S3C2400_GPA11_nCAS0  (1<<11)

    #define S3C2410_GPA12_nGCS1  (1<<12)
    #define S3C2400_GPA12_nCAS1  (1<<12)

    #define S3C2410_GPA13_nGCS2  (1<<13)
    #define S3C2400_GPA13_nGCS1  (1<<13)

    #define S3C2410_GPA14_nGCS3  (1<<14)
    #define S3C2400_GPA14_nGCS2  (1<<14)

    #define S3C2410_GPA15_nGCS4  (1<<15)
    #define S3C2400_GPA15_nGCS3  (1<<15)

    #define S3C2410_GPA16_nGCS5  (1<<16)
    #define S3C2400_GPA16_nGCS4  (1<<16)

    #define S3C2410_GPA17_CLE    (1<<17)
    #define S3C2400_GPA17_nGCS5  (1<<17)

    #define S3C2410_GPA18_ALE    (1<<18)

    #define S3C2410_GPA19_nFWE   (1<<19)

    #define S3C2410_GPA20_nFRE   (1<<20)

    #define S3C2410_GPA21_nRSTOUT (1<<21)

    #define S3C2410_GPA22_nFCE   (1<<22)

    大家看出什么来了吗?如果没有看出来也没有关系,毛毛把上面程序再列写一下你就知道了

    void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
    {
     void __iomem *base = S3C24XX_GPIO_BASE(pin);  //宏计算,得到pin的基地址
     unsigned long ffs = S3C2410_GPIO_OFFSET(pin);      //宏计算,得到pin的相对基地址的偏移地址
     unsigned long flags;
     unsigned long dat;

     local_irq_save(flags);                  //类似于进入临界区                 

     dat = __raw_readl(base + 0x04);           //读I/O口数据
     dat &= ~(1 << offs);
     dat |= to << offs;
     __raw_writel(dat, base + 0x04);            //写I/O口数据

     local_irq_restore(flags);                //类似于退出临界区
    }

    知道了吧,其实就是位的偏移地址,也就是指定读写的位。

    到这里也许大家还是不太明白那到底和几天上午说的arch/arm/mach-s3c2410/include/mach/gpio-nrs.h有什么关系。

    接下来我们慢慢解释:其实我们的每个I/O端口的I/O口数目是0到n,由物理地址上面的四个字节控制(包括读写和配置),四个字节共32位,I/O 0到32对应于,四个自己的每一个位。所以arch/arm/mach-s3c2410/include/mach/gpio-nrs.h文件里面所实现的就是从I/O端口A的0开始到I/O端口H排列成一个从低到高的数字。而这个数字用来为下面的这两个宏来使用,从而求得相对于I/O控制寄存器的起始地址的相对地址和在所在I/O端口寄存器内的偏移位地址。

    #define S3C2410_GPIO_BASE(pin)   ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
    #define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)

    (((pin) & ~31) >> 1)得到相对于起始地址0x56000000的偏移基地址(PORTA求出来为0x00,PORTB求出来为0x10,PORTC求出来为0x20,从而和物理地址相吻合)

    ((pin) & 31)得到在所在控制寄存器的相对位地址

    好了,到此基本上就说完了,希望大家能懂。如果有不懂的地方请给我留言。

  • Linux内核模块参数权限

    2009-04-26 19:38:55

    Linux内核模块参数权限

    在进行linux内核模块编程时,常常需要给模块传递参数,其作用是从使用的设备号到驱动应当任何操作的几个方面. 例如, SCSI 适配器的驱动常常有选项控制标记命令队列的使用, IDE 驱动允许用户控制 DMA 操作. 如果你的驱动控制老的硬件, 还需要被明确告知哪里去找硬件的 I/O 端口或者 I/O 内存地址. 内核通过在加载驱动的模块时指定可变参数的值, 支持这些要求.
           参数常常被声明为一个静态全局变量,如static int num=10;然后使用module_param(参数名,参数类型,参数读写权限)为模块定义一个参数,例如:
    module_prarm(num,int,S_IRUGO);
    这样你就可以在insmod(装载模块)的时候为参数指定值,如果没有指定则使用默认值,例如上面的num=10,则10是参数num的默认值。下面以一个例子说明:
    #include <linux/init.h>
    #include <linux/module.h>
    MODULE_LICENSE("Dual BSD/GPL");
    static int num=10;
    module_param(num,int,S_IRUGO);
    static int hello_init(void)
    {
        printk("Hello module init.\n");
        printk("num=%d\n",num);
        return 0;
    }

    static void   hello_exit(void)
    {
        printk("Goodbye module exit.\n");
    }

    module_init(hello_init);
    module_exit(hello_exit);

    MODULE_DESCRIPTION("a simple module");
    MODULE_ALIAS("hello");
    将以上代码保存为文件hello.c,编写Makefile文件对其进行编译:
    # Makefile2.6
    ifneq ($(KERNELRELEASE),)
    obj-m := hello.o
    else
    PWD  := $(shell pwd)
    KVER ?= $(shell uname -r)
    KDIR := /lib/modules/$(KVER)/build
    all:
        $(MAKE) -C $(KDIR) M=$(PWD)
    endif
    然后make就可以了,make会生成:
    |-- Module.symvers
    |-- built-in.o
    |-- hello.ko
    |-- hello.mod.c
    |-- hello.mod.o
    `-- hello.o
    这些文件,其中的hello.ko就是编译生成的模块,使用insmod将hello.ko模块装载进内核,这个需要超级用户权限,我的系统是ubuntu7.10,所以使用如下命令装载:
    sudo insmod hello.ko num=20
    使用dmesg命令查看运行结果:
    xiyoulinux@xiyoulinux-desktop:~/module$ dmesg #实际上是读取/var/log/messages文件的内容
    ... #省略以前的信息
    [14801.675260] Hello module init.
    [14801.675265] num=20
    这就是运行结果。如果你插入的是sudo insmod hello.ko,那么:
    使用dmesg命令查看运行结果:
    xiyoulinux@xiyoulinux-desktop:~/module$ dmesg #实际上是读取/var/log/messages文件的内容
    ... #省略以前的信息
    [14801.675260] Hello module init.
    [14801.675265] num=10 #取默认值
    以上是内核模块的编译和运行情况,实际上你还需要用到lsmod(查看模块是否被插入,一般在打印出来的第一行里)、rmmod(卸载装载的模块,只有当模块的引用计数为0时才能被卸载)等命令。
           那么实质上当你装载模块hello.ko时,系统会在/sys/module下生成一个hello文件夹:

    xiyoulinux@xiyoulinux-desktop:/sys/module/hello$ tree
    .
    |-- holders
    |-- initstate
    |-- parameters
    |   `-- num
    |-- refcnt
    |-- sections
    |   |-- __param
    |   `-- __versions
    `-- srcversion

    3 directories, 6 files
    其中parameters目录中就存放的是该模块的参数,一个参数对应一个文件,文件的内容为参数的默认值例如:
    xiyoulinux@xiyoulinux-desktop:/sys/module/hello/parameters$ tree
    .
    `-- num

    0 directories, 1 file
    xiyoulinux@xiyoulinux-desktop:/sys/module/hello/parameters$ cat num
    10

    在module_param(num,int,S_IRUGO);
    定义参数时,其中的参数读写权限S_IRUGO其实是对参数文件的读写权限,所以权限的设置值就和对文件的设置值一样,例如上面对num参数权限的设置S_IRUGO就是对所有用户具有读的权限,而S_IRUGO|S_IWUSR 则允许 root 来改变参数。

  • VMware linux 在2.4.20-8 中编译2.6.15.5 内核

    2009-04-26 17:13:22

    软件准备:

    gcc-3.2.2-5.i386.rpm  :用来编译

    ncurses-5.6.tar.gz:用来配合make menuconfig 命令配置内核

    bison-2.4.tar.gz :语法解析器

    flex-2.5.35.tar.bz2 :词法解析器

    m4-1.4.9.tar.gz: 

     

    modutils-2.4.26.tar.bz2 : http://www.kernel.org/pub/linux/utils/kernel/module-init-tools/

    module-init-tools-3.2.2.tar.bz2:  2.6配套工具包

    http://www.kernel.org/pub/linux/utils/kernel/modutils/v2.4/

     

    linux-2.6.15.5.tar.bz2 : 内核源码包

     

    第一步:

        将上面所有软件都拷贝到虚拟机中(非/mnt下)

    第二步:

        将内核源码包linux-2.6.15.5.tar.bz2 放入/usr/src目录中,并用 tar -jxvf linux-2.6.15.5.tar.bz2 将其解压

        ,然后用命令将linxu符号连接映射到linux-2.6.15.5 :ln -s  linux-2.6.15.5  linux

    第三步:rpm -ivh gcc-3.2.2-5.i386.rpm 安装GCC

    第三步:安装ncurses-5.6.tar.gz

               tar zxvf  ncurses-5.6.tar.gz   

               进入解压后目录 ./configure 

               make 

               make install  安装

    第四步:安装bison-2.4.tar.gz

                ./configure --prefix=/usr
                 make

                 make check  :本软件包自带测试套件,能执行一些测试,以确定它是否编译正确

                 make install

    第五步:安装flex-2.5.35.tar.bz2

    ./configure --prefix=/usr &&
    make &&
    make install

    一些程序并不知道flex而是试图寻找lex程序(事实上,flex是实现lex功能的另一种也是更好的选择)。为了满足少数一些程序的需要,我们将创建一个lex脚本,这个脚本调用flex并通过它来模仿lex的输出文件命名惯例。

    通过下面的命令创建一个新文件 /usr/bin/lex : 

    cat > /usr/bin/lex << "EOF"
    #!/bin/sh
    # Begin /usr/bin/lex

    exec /usr/bin/flex -l "$@"

    # End /usr/bin/lex
    EOF
    chmod 755 /usr/bin/lex

     

    第六步:升级m4

    首先要卸载原来的m4,然后再安装新的m4

     

    ./configure --prefix=/usr

                 make

                 make check  :本软件包自带测试套件,能执行一些测试,以确定它是否编译正确

                 make install

     

    第七步:安装modutils-2.4.26.tar.bz2 (如果原来安装了modutils 需要将其卸载:rpm -e --nodeps modutils)

    (由于Linux2.6内核的内核模块处理过程有所改变,因此Linux2.4内核下的modutils工具包已不在适合Linux2.6内核).

     ./configure &&
    make &&
    make install

    (参照:http://docs.huihoo.com/lfs/lfs-4.0/chapter06/modutils.html)

    第八步:安装module-init-tools-3.2.2.tar.bz2

    tar -jxvf module-init-tools-3.2.2.tar.bz2

    ./configure --prefix=/sbin

    make

    make install

    ./generate-modprobe.conf /etc/modprobe.conf

     

    第九步:配置内核

    make menuconfig

     

    第十步:编译内核

    make bzImage (生成使用gzip压缩的内核,生成的文件位于/usr/src/linux/arch/i386/boot目录)

    make modules modules_install(编译内核模块并安装到/lib/modules/2.6.15.5目录)

  • 非常好的将framebuffer驱动的文章,看过后受益匪浅(转

    2009-01-07 19:12:17

    非常好的将framebuffer驱动的文章,看过后受益匪浅
     

    *一、FrameBuffer的原理*
        FrameBuffer 是出现在 2.2.xx 内核当中的一种驱动程序接口。


    Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这-个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操-作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操-作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
        但Framebuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池.CPU将运算后的结果放到这个水池,
    水池再将结果流到显示器.中间不会对数据做处理. 应用程序也可以直接读写这个水池的内容.在这种机制下,
    尽管Framebuffer需要真正的显卡驱动的支持,但所有显示任务都有CPU完成,因此CPU负担很重.
    framebuffer的设备文件一般是 /dev/fb0、/dev/fb1 等等。
    可以用命令: #dd if=/dev/zero ōf=/dev/fb 清空屏幕.
    如果显示模式是 1024x768-8 位色,用命令:$ dd if=/dev/zero ōf=/dev/fb0 bs=1024 count=768
    清空屏幕;
    用命令: #dd if=/dev/fb ōf=fbfile 可以将fb中的内容保存下来;
    可以重新写回屏幕: #dd if=fbfile ōf=/dev/fb;
    在使用Framebuffer时,Linux是将显卡置于图形模式下的.


        在应用程序中,一般通过将 FrameBuffer 设备映射到进程地址空间的方式使用,比如下面的程序就打开 /dev/fb0 设备,并通过
    mmap 系统调用进行地址映射,随后用 memset 将屏幕清空(这里假设显示模式是 1024x768-8 位色模式,线性内存模式):


    int fb;
    unsigned char* fb_mem;
    fb = open ("/dev/fb0", O_RDWR);
    fb_mem = mmap (NULL, 1024*768, PROT_READ|PROT_WRITE,MAP_SHARED,fb,0);
    memset (fb_mem, 0, 1024*768);


        FrameBuffer 设备还提供了若干 ioctl
    命令,通过这些命令,可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩-色模式下的调色板信息等等。
        通过 FrameBuffer
    设备,还可以获得当前内核所支持的加速显示卡的类型(通过固定信息得到),这种类型通常是和特定显示芯片相关的。比如目前最新的内核(2.4.9)中,就包含有-对
    S3、Matrox、nVidia、3Dfx 等等流行显示芯片的加速支持。在获得了加速芯片类型之后,应用程序就可以将 PCI
    设备的内存I/O(memio)映射到进程的地址空间。这些 memio
    一般是用来控制显示卡的寄存器,通过对这些寄存器的操作,应用程序就可以控制特定显卡的加速功能。
        PCI
    设备可以将自己的控制寄存器映射到物理内存空间,而后,对这些控制寄存器的访问,给变成了对物理内存的访问。因此,这些寄存器又被称为"memio"。一旦被映-射到物理内存,Linux
    的普通进程就可以通过 mmap 将这些内存 I/O 映射到进程地址空间,这样就可以直接访问这些寄存器了。
        当然,因为不同的显示芯片具有不同的加速能力,对memio
    的使用和定义也各自不同,这时,就需要针对加速芯片的不同类型来编写实现不同的加速功能。比如大多数芯片都提供了对矩形填充的硬件加速支持,但不同的芯片实现方-式不同,这时,就需要针对不同的芯片类型编写不同的用来完成填充矩形的函数。
        FrameBuffer 只是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备。所以,对于应用程序而言,如果希望在
    FrameBuffer 之上进行图形编程,还需要自己动手完成其他许多工作。


    *二、FrameBuffer在Linux中的实现和机制
    *Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文件为fbcon.c,
    在这个目录下还有与各种显卡驱动相关的源文件。


    (一)、分析Framebuffer设备驱动
        需要特别提出的是在INTEL平台上,老式的VESA
    1.2卡,如CGA/EGA卡,是不能支持Framebuffer的,因为Framebuffer要求显卡支持线性帧缓冲,即CPU可以访问显缓冲中的每一位,-但是VESA
    1.2 卡只能允许CPU一次访问64K的地址空间。
    FrameBuffer设备驱动基于如下两个文件:
    1) linux/include/linux/fb.h
    2) linux/drivers/video/fbmem.c


    下面分析这两个文件。
    1、fb.h
       几乎主要的结构都是在这个中文件定义的。这些结构包括:
    1)fb_var_screeninfo
       这个结构描述了显示卡的特性:


    struct fb_var_screeninfo
    {
    __u32 xres; /* visible resolution */
    __u32 yres;
    __u32 xres_virtual; /* virtual resolution */
    __u32 yres_virtual;
    __u32 xoffset; /* offset from virtual to visible resolution */
    __u32 yoffset;


    __u32 bits_per_pixel; /* guess what */
    __u32 grayscale; /* != 0 Gray levels instead of colors */


    struct fb_bitfield red; /* bitfield in fb mem if true color, */
    struct fb_bitfield green; /* else only length is significant */
    struct fb_bitfield blue;
    struct fb_bitfield transp; /* transparency */


    __u32 nonstd; /* != 0 Non standard pixel format */


    __u32 activate; /* see FB_ACTIVATE_* */


    __u32 height; /* height of picture in mm */
    __u32 width; /* width of picture in mm */


    __u32 accel_flags; /* acceleration flags (hints) */


    /* Timing: All values in pixclocks, except pixclock (of course) */
    __u32 pixclock; /* pixel clock in ps (pico seconds) */
    __u32 left_margin; /* time from sync to picture */
    __u32 right_margin; /* time from picture to sync */
    __u32 upper_margin; /* time from sync to picture */
    __u32 lower_margin;
    __u32 hsync_len; /* length of horizontal sync */
    __u32 vsync_len; /* length of vertical sync */
    __u32 sync; /* see FB_SYNC_* */
    __u32 vmode; /* see FB_VMODE_* */
    __u32 reserved[6]; /* Reserved for future compatibility */

    };


    2) fb_fix_screeninfon
    这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模-式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。

    struct fb_fix_screeninfo {
    char id[16]; /* identification string eg "TT Builtin" */
    unsigned long smem_start; /* Start of frame buffer mem */
    /* (physical address) */
    __u32 smem_len; /* Length of frame buffer mem */
    __u32 type; /* see FB_TYPE_* */
    __u32 type_aux; /* Interleave for interleaved Planes */
    __u32 visual; /* see FB_VISUAL_* */
    __u16 xpanstep; /* zero if no hardware panning */
    __u16 ypanstep; /* zero if no hardware panning */
    __u16 ywrapstep; /* zero if no hardware ywrap */
    __u32 line_length; /* length of a line in bytes */
    unsigned long mmio_start; /* Start of Memory Mapped I/O */
    /* (physical address) */
    __u32 mmio_len; /* Length of Memory Mapped I/O */
    __u32 accel; /* Type of acceleration available */
    __u16 reserved[3]; /* Reserved for future compatibility */

    };


    3) fb_cmap
    描述设备无关的颜色映射信息。可以通过FBIOGETCMAP 和 FBIOPUTCMAP 对应的ioctl操作设定或获取颜色映射信息.

    struct fb_cmap {
    __u32 start; /* First entry */
    __u32 len; /* Number of entries */
    __u16 *red; /* Red values */
    __u16 *green;
    __u16 *blue;
    __u16 *transp; /* transparency, can be NULL */

    };


    4) fb_info
    定义当显卡的当前状态;fb_info结构仅在内核中可见,在这个结构中有一个fb_ops指针, 指向驱动设备工作所需的函数集。

    struct fb_info {
    char modename[40]; /* default video mode */
    kdev_t node;
    int flags;
    int open; /* Has this been open already ? */
    #define FBINFO_FLAG_MODULE 1 /* Low-level driver is a module */
    struct fb_var_screeninfo var; /* Current var */
    struct fb_fix_screeninfo fix; /* Current fix */
    struct fb_monspecs monspecs; /* Current Monitor specs */
    struct fb_cmap cmap; /* Current cmap */
    struct fb_ops *fbops;
    char *screen_base; /* Virtual address */
    struct display *disp; /* initial display variable */
    struct vc_data *display_fg; /* Console visible on this display */
    char fontname[40]; /* default font name */
    devfs_handle_t devfs_handle; /* Devfs handle for new name */
    devfs_handle_t devfs_lhandle; /* Devfs handle for compat. symlink */
    int (*changevar)(int); /* tell console var has changed */
    int (*switch_con)(int, struct fb_info*);
    /* tell fb to switch consoles */
    int (*updatevar)(int, struct fb_info*);
    /* tell fb to update the vars */
    void (*blank)(int, struct fb_info*); /* tell fb to (un)blank the screen */
    /* arg = 0: unblank */
    /* arg > 0: VESA level (arg-1) */
    void *pseudo_palette; /* Fake palette of 16 colors and
    the cursor's color for non
    palette mode */
    /* From here on everything is device dependent */
    void *par;

    };


    5) struct fb_ops
    用户应用可以使用ioctl()系统调用来操作设备,这个结构就是用一支持ioctl()的这些操作的。

    struct fb_ops {
    /* open/release and usage marking */
    struct module *owner;
    int (*fb_open)(struct fb_info *info, int user);
    int (*fb_release)(struct fb_info *info, int user);
    /* get non settable parameters */
    int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con,
    struct fb_info *info);
    /* get settable parameters */
    int (*fb_get_var)(struct fb_var_screeninfo *var, int con,
    struct fb_info *info);
    /* set settable parameters */
    int (*fb_set_var)(struct fb_var_screeninfo *var, int con,
    struct fb_info *info);
    /* get colormap */
    int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con,
    struct fb_info *info);
    /* set colormap */
    int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con,
    struct fb_info *info);
    /* pan display (optional) */
    int (*fb_pan_display)(struct fb_var_screeninfo *var, int con,
    struct fb_info *info);
    /* perform fb specific ioctl (optional) */
    int (*fb_ioctl)(struct inode *inode, struct file *file, unsigned int cmd,
    unsigned long arg, int con, struct fb_info *info);
    /* perform fb specific mmap */
    int (*fb_mmap)(struct fb_info *info, struct file *file, struct
    vm_area_struct *vma);
    /* switch to/from raster image mode */
    int (*fb_rasterimg)(struct fb_info *info, int start);

    };


    6) structure map
    struct fb_info_gen | struct fb_info | fb_var_screeninfo
    | | fb_fix_screeninfo
    | | fb_cmap
    | | modename[40]
    | | fb_ops ---|--->ops on var
    | | ... | fb_open
    | | | fb_release
    | | | fb_ioctl
    | | | fb_mmap
    | struct fbgen_hwswitch -|-> detect
    | | encode_fix
    | | encode_var
    | | decode_fix
    | | decode_var
    | | get_var
    | | set_var
    | | getcolreg
    | | setcolreg
    | | pan_display
    | | blank
    | | set_disp

    [编排有点困难,第一行的第一条竖线和下面的第一列竖线对齐,第一行的第二条竖线和下面的第二列竖线对齐就可以了]
    这个结构 fbgen_hwswitch抽象了硬件的操作.虽然它不是必需的,但有时候很有用.


    2、 fbmem.c
    fbmem.c 处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向-系统内核注册它们自己.
    fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作.


    1) 全局变量


    struct fb_info *registered_fb[FB_MAX];
    int num_registered_fb;


    这两变量记录了所有fb_info 结构的实例,fb_info 结构描述显卡的当前状态,所有设备对应的fb_info
    结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info
    结构就会添加到这个结构中,同时num_registered_fb 为自动加1.


    static struct {
    const char *name;
    int (*init)(void);
    int (*setup)(void);

    } fb_drivers[] __initdata= { ....};


    如果FrameBuffer设备被静态链接到内核,其对应的入口就会添加到这个表中;如果是动态加载的,即使用insmod/rmmod,就不需要关心这个表。

    static struct file_operations fb_ops ={
    owner: THIS_MODULE,
    read: fb_read,
    write: fb_write,
    ioctl: fb_ioctl,
    mmap: fb_mmap,
    open: fb_open,
    release: fb_release


    };


    这是一个提供给应用程序的接口.

    2)fbmem.c 实现了如下函数.


    register_framebuffer(struct fb_info *fb_info);
    unregister_framebuffer(struct fb_info *fb_info);


    这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_inf-o结构然后向系统注册或注销它。


    (二)一个LCD显示芯片的驱动实例
        以Skeleton LCD
    控制器驱动为例,在LINUX中存有一个/fb/skeleton.c的skeleton的Framebuffer驱动程序,很简单,仅仅是填充了
    fb_info结构,并且注册/注销自己。设备驱动是向用户程序提供系统调用接口,所以我们需要实现底层硬件操作并且定义file_operations
    结构来向系统提供系统调用接口,从而实现更有效的LCD控制器驱动程序。


    1)在系统内存中分配显存
    在fbmem.c文件中可以看到, file_operations
    结构中的open()和release()操作不需底层支持,但read()、write()和 mmap()操作需要函数fb_get_fix()的
    支持.因此需要重新实现函数fb_get_fix()。另外还需要在系统内存中分配显存空间,大多数的LCD控制器都没有自己的显存空间,被分配的地址空间的起-始地址与长度将会被填充到fb_fix_screeninfo
    结构的smem_start 和smem_len 的两个变量中.被分配的空间必须是物理连续的。


    2)实现 fb_ops 中的函数
    用户应用程序通过ioctl()系统调用操作硬件,fb_ops 中的函数就用于支持这些操作。(注: fb_ops结构与file_operations
    结构不同,fb_ops是底层操作的抽象,而file_operations是提供给上层系统调用的接口,可以直接调用.
    ioctl()系统调用在文件fbmem.c中实现,通过观察可以发现ioctl()命令与fb_ops's 中函数的关系:
    FBIOGET_VSCREENINFO fb_get_var
    FBIOPUT_VSCREENINFO fb_set_var
    FBIOGET_FSCREENINFO fb_get_fix
    FBIOPUTCMAP fb_set_cmap
    FBIOGETCMAP fb_get_cmap
    FBIOPAN_DISPLAY fb_pan_display


    如果我们定义了fb_XXX_XXX 方法,用户程序就可以使用FBIOXXXX宏的ioctl()操作来操作硬件。


    文件linux/drivers/video/fbgen.c或者linux/drivers/video目录下的其它设备驱动是比较好的参考资料。在所有的这-些函数中fb_set_var()是最重要的,它用于设定显示卡的模式和其它属性,下面是函数fb_set_var()的执行步骤:


    1)检测是否必须设定模式
    2)设定模式


    3)设定颜色映射


    4) 根据以前的设定重新设置LCD控制器的各寄存器。
    第四步表明了底层操作到底放置在何处。在系统内存中分配显存后,显存的起始地址及长度将被设定到LCD控制器的各寄存器中(一般通过fb_set_var()
    函数),显存中的内容将自动被LCD控制器输出到屏幕上。另一方面,用户程序通过函数mmap()将显存映射到用户进程地址空间中,然后用户进程向映射空间发送-的所有数据都将会被显示到LCD显示器上。


    *三、FrameBuffer的应用*


    (一)、一个使用FrameBuffer的例子


       1. FrameBuffer主要是根据VESA标准的实现的,所以只能实现最简单的功能。
       2. 由于涉及内核的问题,FrameBuffer是不允许在系统起来后修改显示模式等一系列操作。(好象很多人都想要这样干,这是不被允许的,当然如果你自己写驱动-的话,是可以实现的).


       3. 对FrameBuffer的操作,会直接影响到本机的所有控制台的输出,包括XWIN的图形界面。


    好,现在可以让我们开始实现直接写屏:


    1、打开一个FrameBuffer设备


    2、通过mmap调用把显卡的物理内存空间映射到用户空间


    3、直接写内存。


    /********************************
    File name : fbtools.h
    */


    #ifndef _FBTOOLS_H_
    #define _FBTOOLS_H_
    #include <linux/fb.h>
    //a framebuffer device structure;
    typedef struct fbdev{
           int fb;
           unsigned long fb_mem_offset;
           unsigned long fb_mem;
           struct fb_fix_screeninfo fb_fix;
           struct fb_var_screeninfo fb_var;
           char dev[20];

    } FBDEV, *PFBDEV;


    //open & init a frame buffer
    //to use this function,
    //you must set FBDEV.dev="/dev/fb0"
    //or "/dev/fbX"
    //it's your frame buffer.
    int fb_open(PFBDEV pFbdev);

    //close a frame buffer
    int fb_close(PFBDEV pFbdev);


    //get display depth
    int get_display_depth(PFBDEV pFbdev);


    //full screen clear
    void fb_memset(void *addr, int c, size_t len);


    #endif


    /******************
    File name : fbtools.c
    */


    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <asm/page.h>
    #include "fbtools.h"
    #define TRUE        1
    #define FALSE       0
    #define MAX(x,y)        ((x)>(y)?(x)y))
    #define MIN(x,y)        ((x)<(y)?(x)y))


    //open & init a frame buffer
    int fb_open(PFBDEV pFbdev)
    {
           pFbdev->fb = open(pFbdev->dev, O_RDWR);
           if(pFbdev->fb < 0)
           {
                  printf("Error opening %s: %m. Check kernel config\n",
    pFbdev->dev);
                  return FALSE;
           }


           if (-1 == ioctl(pFbdev->fb,FBIOGET_VSCREENINFO,&(pFbdev->fb_var)))
           {
                  printf("ioctl FBIOGET_VSCREENINFO\n");
                  return FALSE;
           }


           if (-1 == ioctl(pFbdev->fb,FBIOGET_FSCREENINFO,&(pFbdev->fb_fix)))
           {
                  printf("ioctl FBIOGET_FSCREENINFO\n");
                  return FALSE;
           }


           //map physics address to virtual address
           pFbdev->fb_mem_offset = (unsigned long)(pFbdev->fb_fix.smem_start) &
    (~PAGE_MASK);
           pFbdev->fb_mem = (unsigned long int)mmap(NULL,
    pFbdev->fb_fix.smem_len + pFbdev->fb_mem_offset,              PROT_READ |
    PROT_WRITE, MAP_SHARED, pFbdev->fb, 0);


           if (-1L == (long) pFbdev->fb_mem)
           {
                  printf("mmap error! mem:%d offset:%d\n", pFbdev->fb_mem,
    pFbdev->fb_mem_offset);
                  return FALSE;
           }
           return TRUE;

    }


    //close frame buffer
    int fb_close(PFBDEV pFbdev)
    {
           close(pFbdev->fb);
           pFbdev->fb=-1;


    }


    //get display depth
    int get_display_depth(PFBDEV pFbdev);
    {
           if(pFbdev->fb<=0)
           {
                  printf("fb device not open, open it first\n");
                  return FALSE;
           }
           return pFbdev->fb_var.bits_per_pixel;


    }


    //full screen clear
    void fb_memset (void *addr, int c, size_t len)
    {
        memset(addr, c, len);


    }


    //use by test
    #define DEBUG
    #ifdef DEBUG
    main()
    {
           FBDEV fbdev;
           memset(&fbdev, 0, sizeof(FBDEV));
           strcpy(fbdev.dev, "/dev/fb0");
           if(fb_open(&fbdev)==FALSE)
           {
                  printf("open frame buffer error\n");
                  return;
           }
           fb_memset(fbdev.fb_mem + fbdev.fb_mem_offset, 0,
    fbdev.fb_fix.smem_len);
                  fb_close(&fbdev);


    }


    (二)基于Linux核心的汉字显示的尝试
    我们以一个简单的例子来说明字符显示的过程。我们假设是在虚拟终端1(/dev/tty1)下运行一个如下的简单程序。

    main ( )
    {
    puts("hello, world.\n");

    }


    puts 函数向缺省输出文件(/dev/tty1)发出写的系统调用write(2)。系统调用到linux核心里面对应的核心函数是console.c中的con_w-rite(),con_write()最终会调用do_con_write(
    )。在do_con_write( )中负责把"hello,
    world.\n"这个字符串放到tty1对应的缓冲区中去。
    do_con_write( )还负责处理控制字符和光标的位置。让我们来看一下do_con_write()这个函数的声明。
    static int do_con_write(struct tty_struct * tty, int from_user, const
    unsigned char *buf, int count)
        其中tty是指向tty_struct结构的指针,这个结构里面存放着关于这个tty的所有信息(请参照
    linux/include/linux/tty.h)。Tty_struct结构中定义了通用(或高层)tty的属性(例如宽度和高度等)。在do_con_-write(
    )函数中用到了tty_struct结构中的driver_data变量。driver_data是一个vt_struct指针。在vt_struct结构中包-含这个tty的序列号(我们正使用tty1,所以这个序号为1)。Vt_struct结构中有一个vc结构的数组vc_cons,这个数组就是各虚拟终端的私有-数据。

    static int do_con_write(struct tty_struct * tty, int from_user,const
    unsigned char *buf, int count)
    {
    struct vt_struct *vt = (struct vt_struct
    *)tty->driver_data;//我们用到了driver_data变量
    . . . . .
    currcons = vt->vc_num; file://我们在这里的vc_nums就是1
    . . . . .

    }


    要访问虚拟终端的私有数据,需使用vc_cons〔currcons〕.d指针。这个指针指向的结构含有当前虚拟终端上光标的位置、缓冲区的起始地址、缓冲区大-小等等。
        "hello, world.\n"中的每一个字符都要经过conv_uni_to_pc(
    )这个函数转换成8位的显示字符。这要做的主要目的是使不同语言的国家能把16位的UniCode码映射到8位的显示字符集上,目前还是主要针对欧洲国家的语言-,映射结果为8位,不包含对双字节(double
    byte)的范围。

    这种UNICODE到显示字符的映射关系可以由用户自行定义。在缺省的映射表上,会把中文的字符映射到其他的字符上,这是我们不希望看到也是不需要的。所以我们-有两个选择∶


       1. 不进行conv_uni_to_pc( )的转换。
       2. 加载符合双字节处理的映射关系,即对非控制字符进行1对1的不变映射。
       我们自己定制的符合这种映射关系的UNICODE码表是direct.uni。要想查看/装载当前系统的unicode映射表,可使外部命令loadunima-p。


    经过conv_uni_to_pc( )转换之后,"hello,
    world.\n"中的字符被一个一个地填写到tty1的缓冲区中。然后do_con_write(
    )调用下层的驱动,把缓冲区中的内容输出到显示器上(也就相当于把缓冲区的内容拷贝到VGA显存中去)。


    sw->con_putcs(vc_cons〔currcons〕.d, (u16 *)draw_from, (u16*)draw_to-(u16
    *)draw_from, y, draw_x);


    之所以要调用底层驱动,是因为存在不同的显示设备,其对应VGA显存的存取方式也不一样。
    上面的Sw->con_putcs(
    )就会调用到fbcon.c中的fbcon_putcs()函数(con_putcs是一个函数的指针,在Framebuffer模式下指向
    fbcon_putcs()函数)。也就是说在do_con_write(
    )函数中是直接调用了fbcon_putcs()函数来进行字符的绘制。比如说在256色模式下,真正负责输出的函数是void
    fbcon_cfb8_putcs(struct vc_data *conp, struct display *p,const unsigned
    short *s, int count, int yy, int xx)


    显示中文
        比如说我们试图输出一句中文∶putcs(你好\n
    );(你好的内码为0xc4,0xe3,0xba,0xc3)。这时候会怎么样呢,有一点可以肯定,"你好"肯定不会出现在屏幕上,国为核心中没有汉字字库,中-文显示就是无米之炊了.
        1 在负责字符显示的void fbcon_cfb8_putcs(
    )函数中,原有操作如下∶对于每个要显示的字符,依次从虚拟终端缓冲区中以WORD为单位读取(低位字节是ASCII码,高8位是字符的属性),由于汉字是双字-节编码方式,所以这种操作是不可能显示出汉字的,只能显示出xxxx_putcs()是一个一个VGA字符.


    要解决的问题∶
    确保在do_con_write( )时uni□pc转换不会改变原有编码。一个很直接的实现方式就是加载一个我们自己定制的UNICODE映射表,
    loadunimapdirect.uni,或者直接把direct.uni置为核心的缺省映射表。


    针对如上问题,我们要做的第一个尝试方案是如下。
    首先需要在核心中加载汉字字库,然后修改fbcon_cfb8_putcs()函数,在fbcon_cfb8_putcs(
    )中一次读两个WORD,检查这两个WORD的低位字节是否能拼成一个汉字,如果发现能拼成一个汉字,就算出这个汉字在汉字字库中的偏移,然后把它当成一个16
    x 16的VGA字符来显示。


    试验的结果表明∶


       1. 能够输出汉字,但仍有许多不理想的地方,比如说,输出以半个汉字开始的一串汉字,则这半个汉字后面的汉字都会是乱码。这是半个汉字的问题。
       2. 光标移动会破坏汉字的显示。表现为,光标移动过的汉字会变成乱码。这是因为光标的更新是通过xxxx_putc( )函数来完成的。


    xxxx_putc( )函数与xxxx_putcs(
    )函数实现的功能类似,但是xxxx_putc()函数只刷新一个字符而不是一个字符串,因而xxxx_putc()的输入参数是一个整数,而不是一个字符串的-地址。Xxxx_putc(
    )函数的声明如下∶void fbcon_cfb8_putc(struct vc_data *conp, struct display *p, int
    c, int yy, int xx)


        下一个尝试方案就是同时修改xxxx_putcs(
    )函数和xxxx_putc()函数。为了解决半个汉字的问题,每一次输出之前,都从屏幕当前行的起始位置开始扫描,以确定要输出的字符是否落在半个汉字的位置-上。如果是半个汉字的位置,则进行相应的调整,即从向前移动一个字节的位置开始输出。
        这个方案有一个困难,即xxxx_putc( )函数不用缓冲区的地址,而是用一个整数作为参数。所以xxxx_putc(
    )无法直接利用相邻的字符来判别该定符是否是汉字。
        解决方案是,利用xxxx_putc( )的光标位置参数(yy,
    xx),可以逆推出该字符在缓冲区中的位置。但仍有一些小麻烦,在Linux的虚拟终端下,用户可能会上卷该屏幕(shift +
    pageup),导致光标的y座标和相应字符在缓冲区的行数不一致。相应的解决方案是,在逆推的过程中,考虑卷屏的参量。


    这样一来,我们就又进了一步,得到了一个相对更好的版本。但仍有问题没有解决。敲入turbonetcfg,会发现菜单的边框字符也被当成汉字显示。这是因为,-这种边框字符是扩展字符,也使用了字符的第8位,因而被当作汉字来显示。例如,单线一的制表符内码为0xC4,当连成一条长线就是由一连串0xC4组成,而0x-C4C4正是汉字哪。于是水平的制表符被一连串的哪字替代了。要解决这个问题就非常不容易了,因为制表符的种类比较多,而且垂直制表符与其后面字符的组合型式又-多种多样,因而很难判断出相应位置的字符是不是制表符,从理论上说,无论采取什么样的排除算法,都必然存在误判的情况,因为总存在二义性,没有充足的条件来推断-出当前字符究竟是制表符还是汉字。


    我们一方面寻找更好的排除组合算法,一方面试图寻找其它的解决方案。要想从根本上解决定个问题,必须利用其它的辅助信息,仅仅从缓冲区的字符来判断是不够的。
        经过一番努力,我们发现,在UNIX中使用扩展字符时,都要先输出字符转义序列(Escape
    sequence)来切换当前字符集。字符转义序列是以控制字符Esc为首的控制命令,在UNIX的虚拟终端中完成终端控制命令,这种命令包括,移动光标座标、-卷屏、删除、切换字符集等等。也就是说在输出代表制表符的字符串之前,通常是要先输出特定的字符转义序列。
    在console.c里,有根据字符转义序列命令来记录字符状态的变量。结合该变量提供的信息,就可以非常干净地把制表符与汉字区别开来。


        在如上思路的指引下,我们又产生了新的解决方案。经过改动得到了另一各版本.


    在这个新版本上,turbonetcfg在初次绘制的时候,制表符与汉字被清晰地区分开来,结果是非常正确的。但还有新的问题存在∶turbonetcfg
    在重绘的时候(如切换虚拟终端或是移动鼠标光标的时候),制表符还是变成了汉字,因为重绘完全依赖于缓冲区,而这时用来记录字符集状态的变量并不反映当前字符集-状态。问题还是没有最终解决。我们又回到了起点。∶(
    看来问题的最终解决手段必须是把字符集的状态伴随每一个字符存在缓冲区中。让我们来研究一下缓冲区的结构。每一个字符占用16bit的缓冲区,低8位是ASCI-I值,完全被利用,高8位包含前景颜色和背景颜色的属性,也没有多余的空间可以利用。因而只能另外开辟新的缓冲区。为了保持一致性,我们决定在原来的缓冲区后面-添加相同大小的缓冲区,用来存放是否是汉字的信息。


    也许有读者会问,我们只需要为每个字符添加一bit的信息来标志是否是汉字就足够了,为什么还要开辟与原缓冲区大小相同的双倍缓冲区,是不是太浪费呢?我们先放-下这个问题,稍后再作回答。


    其实,如果再添加一bit来标志是当前字符是汉字的左半边还是右半边的话,就会省去扫描屏幕上当前整行字符串的工作,这样一来,编程会更简单。但是有读者会问,-即使是这样,使用8bit总够用了吧?为什么还要使用16bit呢?
        我们的作法是∶用低8位来存放汉字另外一半的内码,用高8位中的2
    bit来存放上面所讲的辅助信息,高8位的剩余6位可以用来存放汉字或其它编码方式(如BIG5或日文、韩文)的信息,从而使我们可以实现同屏显示多种双字节语-言的字符而不会有相互干扰。另外,在编程时,双倍缓冲也比较容易计算。这样我们就回答了如上的两个问题。
        迄今为止,我们有了一套彻底解决汉字和制表符相互干扰、半个汉字的刷新、重绘等问题的方案。剩下的就是具体编程实现的问题了。
        但是,由于Framebuffer的驱动很多,修改每一个驱动的xxxx_putc()函数和xxxx_putcs(
    )函数会是一项不小的工作,而且,改动驱动程序后,每种驱动的测试也是很麻烦的,尤其是对于有硬件加速的显卡,修改和测试会更不容易。那么,存不存在一种不需要-修改显卡驱动程序的方法呢?
        经过努力,我们发现,可以在调用xxxx_putcs(
    )或xxxx_putc()函数输出汉字之前,修改vga字库的指针使其指向所需显示的汉字在汉字字库中的位置,即把一个汉字当成两个vga
    ASCII字符输出。也就是说,在内核中存在两个字库,一个是原有的vga字符字库,另一个是汉字字库,当我们需要输出汉字的时候,就把vga字库的指针指向汉-字字库的相应位置,汉字输出完之后,再把该指针指向vga字库的原有位置。
       这样一来,我们只需要修改fbcon.c和console.c,其中console.c负责维护双倍缓冲区,把每一个字符的信息存入附加的缓冲区;
    而fbcon.c负责利用双倍缓冲区中附加的信息,调整vga字库的指针,调用底层的显示驱动程序。这里还有几个需要注意的地方∶


       1. 由于屏幕重绘等原因,调用底层驱动xxxx_putc(
       )和xxxx_putcs()的地方有多处。我们作了两个函数分别包装这两个调用,完成替换字库、调用xxxx_putcs( )或xxxx_putc(
       )、恢复字库等功能。
       2. 为了实现向上滚屏(shift + pageup)时也能看到汉字,我们需要作另外的修改。
           Linux 在设计虚拟终端的时候,提供了回顾被卷出屏幕以外的信息的功能,这就是用热键来向上滚屏(shift +
       pageup)。当前被使用的虚拟终端拥有一个公共的缓冲区(soft
       back),用来存放被滚出屏幕以外的信息。当切换虚拟终端的时候,公共缓冲区的内容会被清除而被新的虚拟终端使用。向上滚屏的时候,显示的是公共缓冲区中的内-容。因此,如果我们想在向上滚屏的时候看到汉字,公共缓冲区也必须加倍,以确保没有信息丢失。当滚出屏幕的信息向公共缓冲区填写的时候,必须把相应的附加信息也-填写进公共缓冲区的附加区域。
       这就要求fbcon.c必须懂得利用公共缓冲区的附加信息。
           当然,有另外一种偷懒的方法,那就是不允许用户向上滚屏,从而避免对公区缓冲区的处理。
       3. 把不同的编码方式(GB、BIG5、日文和韩文)写成不同的module,以实现动态加载,从而使得扩展新的编码方式不需要重新编译核心。


    测试


    本文实现的Kernel Patch文件(patch.kernel.chinese)可以从http://www.turbolinux.com.cn下载。Cd
    /usr/src/(该目录下应有Linux核心源程序所在的目录linux/) patch -p0 -b <
    patch.kernel.chinesemake menuconfig 请选择Console drivers选项中的


    〔*〕 Double Byte Character Display Support(EXPERIMENTAL)
    〔*〕 Double Byte GB encode (module only)
    〔*〕 VESA VGA graphics console
    <*> Virtual Frame Buffer support (ONLY FOR TESTING!)
    <*> 8 bpp packed pixels support
    <*> 16 bpp packed pixels support
    <*> VGA characters/attributes support
    〔*〕 Select compiled-in fonts
    〔*〕VGA 8x8 font
    〔*〕VGA 8x16 font


    make dep
    make bzImage
    make modules
    make install
    make modules_install


    然后用新的核心启动。


    Insmod encode-gb.o


    *四、其它*


    (一)   设置FrameBuffer
        FrameBuffer,可以译作"帧缓冲",有时简称为
    fbdrv,基于fbdrv的console也被称之为fbcon。这是一种独立于硬件的抽象图形设备。FrameBuffer的优点在于其高度的可移植
    性、易使用性、稳定性。使用Linux内核的
    FrameBuffer驱动(vesafb),可以轻松支持到1024X768X32bpp以上的分辩率。而且目前可得到的绝大多数linux版本所发行
    的内核中,已经预编译了FrameBuffer支持,通常不需要重新编译内核就可以使用。所以FrameBuffer也是zhcon推荐使用的驱动方式。


    进入FrameBuffer可以简单地在系统启动时向kernel传送vga=mode-number的参数来激活FrameBuffer设备,如:
    lilo:linux vga=305
    将会启动1024x768x8bpp模式。


               640x480    800x600    1024x768    1280x1024
    8 bpp      769          771       773        775
    16 bpp     785          788       791        794
    32 bpp     786          789       792        795


    (二)   要使linux缺省进入FrameBuffer,可以修改/etc/lilo.conf,加入一下语句:
    vga=0x303
    退出编辑,执行:
    lilo -v
    重新启动linux,可以使其进入800x600的256色模式。
    grub也是一样,在grub.conf中的kernel行后面写上vga=xxx就行了,也可以用vga=ask,让系统启动的时候询问你用多大的分辨率


    (三)我编译内核时,选择framebuffer模式,启动时屏幕上有一企鹅图片,不知这是如何造成的这个图片可以去掉或改动吗?
    可以将drivers/video/fbcon.c: fbcon_setup()中if (logo) { } 代码去掉。

Open Toolbar