Linux应用程序开发 基础知识 - 技术文档 - 新手入门 Linux时代 - 开源、自...

来源:百度文库 编辑:神马文学网 时间:2024/04/27 21:54:59
Table of Contents
15.1. 设备号
15.2. 设备号的分配和释放
15.3. 重要的数据结构
15.4. 读和写
15.1. 设备号
字符设备在系统中以设备文件的形式表示,位于/dev目录下。每个字符设备都有一个主设备号和次设备号,主设备号标识设备对应的驱动程序,次设备号标识设备文件所指的具体设备。
主次设备号的数据类型是dev_t,在/linux/types.h中定义。在2.6内核中,dev_t是一个32位的数,其中12位用来表示主设备号,其余20位用来表示次设备号。要获得设备的主次设备号可以使用内核提供的宏:
MAJOR(dev_t dev);        #获得主设备号
MINOR(dev_t dev);        #获得次设备号
这些宏定义位于linux/kdev_t.h中。如果要把主次设备号转换成dev_t类型,则可使用:
MKDEV(int major, int minor);
15.2. 设备号的分配和释放
在建立一个字符设备之前,需要为它分配一个或多个设备号。使用register_chrdev_region()函数完成设备号的分配。该函数在linux/fs.h中声明。原型如下:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first:是要分配的主设备号范围的起始值,次设备号一般设置为0;
count:是所请求的连续设备号的个数;
name:是和该设备号范围关联的设备名称,它将出现在/proc/devices或/sysfs中。
如果分配成功则返回0,分配失败则返回一个负的错误码,所请求的设备号无效。
还有一个自动分配设备号的函数alloc_chrdev_region(),原型如下:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev:       自动分配到设备号范围中的第一个主设备号;
firstminor:自动分配的第一个次设备号,通常为0;
count:     是所请求的连续设备号的个数;
name:      是和该设备号范围关联的设备名称,它将出现在/proc/devices或/sysfs中。
如果我们不再使用设备号,则要使用unregister_chrdev_region()函数释放它。函数原型如下:
void unregister_chrdev_region(dev_t first, unsigned int count);
函数的参数作用同上
我们一般在模块的清除函数中调用设备号释放函数。
在内核源码目录的Documentation/devices.txt文件中列出了已静态分配给常用设备的主设备号。为了减少设备号分配的冲突,我们一般要使用alloc_chrdev_region()函数来自动分配设备号。
15.3. 重要的数据结构

件操作结构:struct
file_operations,在linux/fs.h中定义。它包含一组函数指针,实现文件操作的系统调用,如read、write等。每个打开的文
件都和一个文件操作结构关联(通过file结构中指向file_operations结构的f_op字段进行关联)。
文件结构:struct file,在linux/fs.h中定义。file结构代表一个打开的文件,由内核在open时创建。指向文件结构的指针在内核中通常称为filp(文件指针)。当文件的所有实例都被关闭之后,内核会释放这个数据结构。
节点结构:struct inode,在linux/fs.h中定义。inode结构是内核表示文件的方法,而file结构是以文件描述符的方式表示文件的方法。结构中以下两个字段对编写驱动程序有用:

  • dev_t i_rdev,该字段包含了真正的设备编号。

  • struct cdev *i_cdev,该字段包含指向struct cdev结构的指针。

      从设备的inode获取主次设备号的宏:
      unsigned int iminor(struct inode *inode);
      unsigned int imajor(struct inode *inode);
      15.4. 读和写
      下面两个是字符设备读写操作最重要的内核函数。
      unsigned long copy_to_user (void __user * to, const void * from, unsigned long n);
      读操作,把数据从内核空间复制到用户空间,返回不能复制的字节数,如果成功则返回0。
      to      目的地址,在用户空间中;
      from    源地址,在用户空间;
      n       要复制的字节数。
      unsigned long copy_from_user (void * to, const void __user * from, unsigned long n);
      写操作,把数据从用户空间复制到内核空间,返回不能复制的字节数,如果成功则返回0。
      to      目的地址,在内核空间中;
      from    源地址,在用户空间;
      n       要复制的字节数。
      Chapter 16. PCI设备
      pci
      设备上电时,硬件保持未激活状态。设备不会有内存和I/O端口映射到计算机的地址空间。每个PCI主板上都配备有能够处理PCI的BIOS、NVRAM或
      PROM等固件。这些固件通过读写PCI控制器中的寄存器,提供了对设备配置地址空间的访问。系统引导时,固件在每个PCI设备上执行配置事务,以便为它
      提供的每个地址区域分配一个安全的位置。当驱动程序访问设备时,它的内存和I/O区域已经被映射到了处理器的地址空间。
      所有PCI设备都有至少256字节的地址空间。前64字节是标准化的,每种设备都有且意义相同,其余字节是设备相关的。
      在内核中有三个主要的数据结构与PCI接口有关,在开发PCI设备驱动程序时要用到,分别是:

    • pci_device_id,PCI设备类型的标识符。在include/linux/mod_devicetable.h头文件中定义。
      struct pci_device_id {
              __u32 vendor, device;           /* Vendor and device ID or PCI_ANY_ID*/
              __u32 subvendor, subdevice;     /* Subsystem ID's or PCI_ANY_ID */
              __u32 class, class_mask;        /* (class,subclass,prog-if) triplet */
              kernel_ulong_t driver_data;     /* Data private to the driver */
      };
      PCI设备的vendor、device和class的值都是预先定义好的,通过这些参数可以唯一确定设备厂商和设备类型。这些PCI设备的标准值在include/linux/pci_ids.h头文件中定义。
      pci_device_id需要导出到用户空间,使模块装载系统在装载模块时知道什么模块对应什么硬件设备。宏MODULE_DEVICE_TABLE()完成该工作。
      设备id一般用数组形式。如:
      static struct pci_device_id rtl8139_pci_tbl[] = {
              {0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
              ....
      };
      MODULE_DEVICE_TABLE (pci, rtl8139_pci_tbl);

    • pci_dev,标识具体的PCI设备实例,与net_device类似。内核通过该内核结构来访问具体的PCI设备。在include/linux/pci.h头文件中定义。

    • pci_driver,设备驱动程序数据结构,它是驱动程序与PCI总线的接口,有大量的回调函数和指针,向PCI核心描述了PCI驱动程序。在include/linux/pci.h头文件中定义。
      static struct pci_driver rtl8139_pci_driver = {
              .name           = DRV_NAME,                         #设备名
              .id_table       = rtl8139_pci_tbl,                  #pci设备的id表组
              .probe          = rtl8139_init_one,                 #初始化函数
              .remove         = __devexit_p(rtl8139_remove_one),  #退出函数
      #ifdef CONFIG_PM                                            #如果设备支持电源管理
              .suspend        = rtl8139_suspend,                  #休眠
              .resume         = rtl8139_resume,                   #从休眠恢复
      #endif /* CONFIG_PM */
      };

        内核通过pci_register_driver和pci_unregister_driver函数
        来注册和注消PCI设备驱动程序。这两个函数在drivers/pci/pci.c源码中定义。pci_register_driver函数需要使用
        pci_driver数据结构作为参数。通过注册,PCI设备就与PCI设备驱动程序关联起来了。
        PCI设备最大的优点是可以自动探测每个设备所需的IRQ和其它资源。有两种探测方式,一种是静态探测,一种是动态探测。静态探测是通过设备驱动程序自动选择相关资源,动态探测是指支持热插拔设备的功能。
        PCI设备通过pci_driver结构中的suspend和resume函数指针支持电源管理。可实现暂停和重新启动PCI设备的功能。
        /lib/modules/KERNEL_VERSION/modules.pcimap文件列出内核所支持的所有PCI设备和它们的模块名。
        debian:/lib/modules/2.6.23.9# cat modules.pcimap | more
        # pci module         vendor     device     subvendor  subdevice  class      class_mask driver_data
        snd-trident          0x00001023 0x00002000 0xffffffff 0xffffffff 0x00040100 0x00ffff00 0x0
        snd-trident          0x00001023 0x00002001 0xffffffff 0xffffffff 0x00000000 0x00000000 0x0
        ...
        8139cp               0x000010ec 0x00008139 0xffffffff 0xffffffff 0x00000000 0x00000000 0x0
        8139cp               0x00000357 0x0000000a 0xffffffff 0xffffffff 0x00000000 0x00000000 0x0
        ...
        Chapter 17. 内核初始化优化宏
        内核使用了大量不同的宏来标记具有不同作用的函数和数据结构。如宏__init、__devinit等。这些宏在include/linux/init.h头文件中定义。编译器通过这些宏可以把代码优化放到合适的内存位置,以减少内存占用和提高内核效率。
        下面是一些常用的宏:

      • __init,标记内核启动时使用的初始化代码,内核启动完成后不再需要。以此标记的代码位于.init.text内存区域。它的宏定义是这样的:
        #define _ _init    _ _attribute_ _ ((_ _section_ _ (".text.init")))

      • __exit,标记退出代码,对于非模块无效。

      • __initdata,标记内核启动时使用的初始化数据结构,内核启动完成后不再需要。以此标记的代码位于.init.data内存区域。

      • __devinit,标记设备初始化使用的代码。

      • __devinitdata,标记初始化设备数据结构的函数。

      • __devexit,标记移除设备时使用的代码。

      • xxx_initcall,一系列的初始化代码,按降序优先级排列。
                                   初始化代码的内存结构
          _init_begin              -------------------
                                  |  .init.text       | ---- __init
                                  |-------------------|
                                  |  .init.data       | ---- __initdata
          _setup_start            |-------------------|
                                  |  .init.setup      | ---- __setup_param
          __initcall_start        |-------------------|
                                  |  .initcall1.init  | ---- core_initcall
                                  |-------------------|
                                  |  .initcall2.init  | ---- postcore_initcall
                                  |-------------------|
                                  |  .initcall3.init  | ---- arch_initcall
                                  |-------------------|
                                  |  .initcall4.init  | ---- subsys_initcall
                                  |-------------------|
                                  |  .initcall5.init  | ---- fs_initcall
                                  |-------------------|
                                  |  .initcall6.init  | ---- device_initcall
                                  |-------------------|
                                  |  .initcall7.init  | ---- late_initcall
          __initcall_end          |-------------------|
                                  |                   |
                                  |    ... ... ...    |
                                  |                   |
          __init_end              -------------------
          初始化代码的特点是:在系统启动运行,且一旦运行后马上退出内存,不再占用内存。
          对于驱动程序模块来说,这些优化标记使用的情况如下:

        • 通过module_init()和module_exit()函数调用的函数就需要使用__init和__exit宏来标记。

        • pci_driver数据结构不需标记。

        • probe()和remove()函数应该使用__devinit和__devexit标记,且只能标记probe()和remove()

        • 如果remove()使用__devexit标记,则在pci_driver结构中要用__devexit_p(remove)来引用remove()函数。

        • 如果你不确定需不需要添加优化宏则不要添加。
            Chapter 18. 访问内核参数的接口
            内核通过不同的接口向用户输出内核信息。我们可通过这些接口访问和修改内核参数。共有三种接口,其中两种是procfs和sysfs虚拟文件系统,第三种是sysctl命令。


          • 用procfs虚拟文件系统的内核选项是"Filesystems-->Pseudo filesystems-->proc file
            system support"。procfs文件系统挂载在/proc目录,可用cat、more等shell命令查看目录中的文件。

          • sysctl命令也可以修改和查看内核变量,sysctl操作的内核变量位于/proc/sys目录下。启用sysctl支持的内核选项是"General setup-->Sysctl support"。

          • procfs
            和sysctl接口已使用多年,从2.6内核开始,引入新的sysfs虚拟文件系统,它挂载在/sys目录下。启用sysfs的内核选项
            是"Filesystems-->Pseudo filesystems-->sysfs filesystem support
            (NEW)"。sysfs以更整齐更直观的方式向用户展示了内核的各种参数。/proc将会向sysfs迁移。


              外,通过ioctl(input/output control)system
              call和Netlink接口也可以向内核发送命令,执行内核参数配置工作,大多数的网络配置参数都可以用这两个接口修改。ifconfig和route
              命令使用ioctl接口,IPROUTE2使用Netlink接口。
              网络的ioctl命令在include/linux
              /sockios.h中定义。这些命令被定义成类似于SIOCSIFMTU的宏,宏的命令规则是这样的,开头四个字符SIOC代表ioctl命令;S表示
              set,G表示get;if表示接口类型;MTU表示mtu。其它字符的表示方式还有:ADD表示添加,RT表示路由等。
              Chapter 19. 内核初始化选项
              我们可以通过内核初始化选项,在系统启动时或内核模块加载时微调内核的功能。
              模块的初始化选项是通过模块程序中的module_param宏传递的。如:
              ...
              module_param(multicast_filter_limit, int, 0444);
              module_param(max_interrupt_work, int, 0444);
              module_param(debug, int, 0444);
              ...
              module_param宏的第一个参数是选项名,可在/sys虚拟文件系统中该模块的parameter目录中中查看到。第二个参数是选项类型,第三个参数是选项的值。上面的宏是sis900网卡的模块选项。在我的系统中显示为:
              debian:/sys/module/sis900/parameters# ls -l
              总计 0
              -r--r--r-- 1 root root 4096 2007-12-20 11:51 max_interrupt_work
              -r--r--r-- 1 root root 4096 2007-12-20 11:51 multicast_filter_limit
              -r--r--r-- 1 root root 4096 2007-12-20 11:51 sis900_debug
              debian:/sys/module/sis900/parameters#           
              Chapter 20. 内核模块编程
            • Linux应用程序开发 基础知识 - 技术文档 - 新手入门 Linux时代 - 开源、自... GCC使用手册及常用命令行 - 技术文档 - 新手入门 Linux时代 - 开源、自由、共... linux下多进程、多线程编程 - 技术文档 - 程序开发 Linux时代 - 开源、自由... 关于linux图形界面编程基本知识 - 技术文档 - 程序开发 Linux时代 - 开源、... 我的计划--嵌入式学习路径 - 技术文档 - 程序开发 Linux时代 - 开源、自由、共... 我的计划--嵌入式学习路径 - 技术文档 - 程序开发 Linux时代 - 开源、自由、共... 加速Ubuntu的开机过程(上) - 技术文档 - 新手入门 Linux时代 - 阿里巴巴... gcc编译选项介绍(转) - 技术文档 - 程序开发 Linux时代 - 开源、自由、共享 - 中国最大的Linux技术社区 命令:du 用法 linux文件夹大小 - 技术文档 - 系统管理 Linux时代 - 开源、自由、共享 - 中国最大的Linux技术社区 linux内核链表 - 技术文档 - 系统管理 Linux时代 - 开源、自由、共享 - ... linux下解压命令详解 - 技术文档 - 系统管理 Linux时代 - 开源、自由、共享... linux下解压命令详解 - 技术文档 - 系统管理 Linux时代 - 开源、自由、共享... Linux启动过程综述 - 技术文档 - 安装启动 Linux时代 - 开源、自由、共享 ... 在debian下配置bugzilla - 技术文档 - 系统管理 Linux时代 - 开源、自由、共享 - 中国最大的Linux技术社区 u-boot源码分析 - 技术文档 - 系统管理 Linux时代 - 开源、自由、共享 -... 网卡的组成工作原理 - 技术文档 - 安装启动 Linux时代 - 开源、自由、共享 - ... MySQL默认字符编码的设置 - 技术文档 - 数据库 Linux时代 - 开源、自由、共... Ubuntu上nfs的安装配置 - 技术文档 - 系统管理 Linux时代 - 开源、自由... Linux2.6内核驱动与2.4的区别 - 技术文档 - 系统管理 Linux时代 - 开源、自由、共享 - 中国最大的Linux技术社区 网络监控-iptraf安装配置使用中文文档 - Linux - linux新手入门 - L... Linux系统基础开发技术:构建Linux 库文件 Fedora/Redhat 在线安装更新软件包,yum 篇 ── 给新手指南(转) - 技术文档 - 安装启动 Linux时代 - 开源、自由、共享 - 中国最大的Linux技术社区 I2Key技术文档 - 用MS.NET开发三层结构应用程序 非常全面的NFS文档(Linux新手入门与安装配置论坛)