IBM AIX 设备驱动程序开发

AIX 设备驱动程序框架及相关 API 的教程

本教程阐述 IBM® AIX® 设备驱动程序框架及相关的应用程序编程接口 (API)。在这里,我们将讨论设备交换结构、注册设备交换方法的 API、主编号和次编号管理,以及将数据从内核移动到用户空间。

Gautam Raut, 高级软件工程师, IBM China

http://www.ibm.com/developerworks/aix/library/au-aix-device-driver/GautamRaut.JPGGautam Raut 在印度浦那的 IBM 软件实验室工作,是 Andrew File System (AFS) Team 中的一名高级软件工程师。他致力于对转储和崩溃,以及 Linux 和 AIX 平台上所报告的错误进行内核与用户级的调试。他还研究各种 AFS 特定的 AIX 内核组件。Gautam 持有浦那大学的计算机科学硕士学位。他是一个 Linux 爱好者,他用业余时间在他的 Fedora 8 机器上研究 Linux 内核。



2013 年 6 月 13 日

简介

在传统的 UNIX® 中,术语 设备 是指硬件组件,如磁盘驱动器、磁带驱动器、打印机、键盘、伪设备(如控制台、错误特殊文件和空特殊文件),等等。在 AIX 中,这些设备被称为核心设备,它们有设备驱动程序,系统通过主编号和次编号来识别它们。

AIX 设备驱动程序是一种 AIX 内核扩展。内核扩展在一个受保护的内核域上运行。它们可以在系统启动或运行时被加载到内核中,也可以在运行时被删除。用户级的代码只能通过系统调用来访问注册的设备驱动程序代码。设备驱动程序为 AIX 添加了系统管理的可扩展性、可配置性和易用性。

如需了解基本的内核扩展开发,请参阅 IBM developerWorks 文章 编写 AIX 内核扩展

设备驱动程序通过引用 /usr/lib/kernex.exp 使用由 AIX 内核 "/unix" 导出的核心内核服务。"kernex.exp" 包含一个由内核导出的符号列表。这些导出的符号本质上是内核函数和存储位置(内核全局数据结构)。链接器程序 (ld) 在链接编译的设备驱动程序代码时使用此信息。


设备类型

  • 字符
  • 网络
  • 多路复用字符设备

设备被表示为在 /dev 目录下面所列出的特殊类型的文件。

这些文件中没有任何可打印的内容。实际上,它们提供一个接口,供用户与相关设备进行交互。这些文件的 inode 包括主编号和次编号。使用 mknod() 系统调用可以创建这些文件。


Object Data Manager (ODM)

ODM 维护以对象的类的形式所表示的数据。它是用于存储特定于设备的配置信息。它有预定义的数据库,用于存储所有设备的配置数据。它还具有当前在系统中运行的设备实例的自定义数据库。ODM 需要初始化之后才能使用。打算配置设备驱动程序的应用程序,必须首先初始化 ODM。

应用程序使用在 /usr/include/odmi.h 中定义的 odm_initialize() API 初始化 ODM。


内核扩展配置的基础知识

struct cfg_load 的组件和使用

加载内核扩展的结构:

struct cfg_load
{
   caddr_t path; /* ptr to object module pathname */
   caddr_t libpath; /* ptr to a substitute libpath */
   mid_t   kmid;    /* kernel module id (returned) */
};

struct cfg_dd 的组件和使用

用于调用设备驱动程序的配置(模块)入口点的结构:

struct cfg_dd
{
  mid_t   kmid;/* Module ID of device driver */
  dev_t   devno; /* Device major/minor number */
  int     cmd; /* Config command code for device driver*/
  caddr_t ddsptr; /* Pointer to device dependent structure*/
  int     ddslen; /* Length of device dependent structure */
};

Sysconfig() 系统调用

Sysconfig() 需要三个参数。

int sysconfig(             
   int cmd, /* Command to be executed */
   void *parmp,/* Address of structure containing info for cmd */
   int parmlen /* Length of parmp information */
 );

Sysconfig() 用于控制内核扩展的生命周期。如 /usr/include/sys/sysconfig.h 中所示,下面的命令被传递给 sysconfig() 作为 cmd 参数。

  • SYS_KLOAD: 在内核内存中加载一个内核扩展目标文件。
  • SYS_SINGLELOAD: 只有在尚未加载内核扩展目标文件时加载它。
  • SYS_QUERYLOAD: 确定是否加载一个指定的内核目标文件。
  • SYS_KULOAD: 卸载以前加载的内核目标文件。
  • SYS_CFGKMOD: 在模块入口点调用指定的模块,用于配置。

设备驱动程序基础知识

主编号和次编号

设备被视为 /dev 目录下的特殊文件。因此,每个设备都有一个名称,并且在其根文件系统下面有一个关联的 inode 或索引节点。每一个设备的文件系统入口点都包含主编号和次编号。

主编号用于在系统中惟一地找到该设备。这是设备交换表的索引。设备交换表包含设备特定的方法的指针,它们基本上是通用文件系统函数的实现。以这种方式,给定移动设备上由用户进程发布的文件系统调用被解析为对相应设备驱动程序函数的调用。驱动程序在内部使用次编号来区分多路复用设备的逻辑通道。

通过库调用 genmajor() 可以获得主编号。

genmajor() 的语法是:

	int genmajor(char* name_of_device_driver)

通过库调用 genminor() 可以获得次编号。

它生成可供设备使用的最小的未使用次编号、首选的次编号码(如果可用),或为设备生成一组未使用的次编号。

genminor() 的语法是:

int *genminor (
 char * device_instance,
 /*
 Points to a character string containing the device instance name.*/
 int major_no,
 /*
  The major number of the device instance.*/
 int preferred_minor,
 /*
  Contains a single preferred minor number or a starting  
minor number for generating a set of numbers. */
 int minors_in_grp,
 /*
 Indicates how many minor numbers are to be allocated.	*/
 int inc_within_grp,
 /*
 Indicates the interval between minor numbers.*/
 int inc_btwn_grp
 /*
 Indicates the interval between groups of minor numbers.*/
);

makedev() API

给定设备的主编号和次编号需要被打包成一个 32 位或 64 位整数,这取决于架构。该整数的数据类型是 dev_tmakedev() 是一个宏,它通过给定的主编号和次编号创建 dev_t。它是在 <sys/sysmacros.h> 下面定义的。makedev64() 用于为 64 位环境创建 dev_t

makedev()makedev64() 的语法是:

 dev_t makedev64(int major, int minor);

mknod() 系统调用

它是在 <sys/stat.h> 中定义的。它用于创建一个普通文件、先进先出 (FIFO),或一个特殊文件。它要求 root 特权,以使用 mknod() 来创建一个设备特殊文件。

mknod() 的语法是:

 int mknod (
  char *Path,
  /*
   Names the new device special file.
  */
  int Mode,
  /*
   Specifies the file type, attributes, and access
   permissions.
  */
  dev_t Device
  /*
   Device number generated by makedev() subroutine.
  */
 );

设备交换表

设备交换表是 struct devsw 结构的一个数组。它按主编号索引,并驻留在内存中(也就是说,它永远不会被换出 RAM)。块设备和字符设备使用此内核结构在根文件系统上注册自己。

设备交换表条目结构:

struct devsw
{
 int (*d_open)(); /* entry point for open routine */
 int (*d_close)(); /* entry point for close routine */
 int (*d_read)();/* entry point for read routine */
 int (*d_write)(); /* entry point for write routine */
 int (*d_ioctl)();/* entry point for ioctl routine */
 int (*d_strategy)();/* entry point for strategy routine */
 struct tty  *d_ttys;/* pointer to tty device structure */
 int (*d_select)();  /* entry point for select routine */
 int (*d_config)();  /* entry point for config routine */
 int (*d_print)(); /* entry point for print routine */
 int (*d_dump)(); /* entry point for dump routine */
 int (*d_mpx)(); /* entry point for mpx routine */
 int (*d_revoke)(); /* entry point for revoke routine */
 caddr_t d_dsdptr; /* pointer to device specific data */
 /*
  * The following entries are control fields managed
  * by the kernel and should not be modified by device
  * drivers or kernel extensions.  They may be set to
  * 0 in the devsw structure for devswadd(), since they
  * are not copied into the device switch table.
  */
 caddr_t d_selptr;/* ptr to outstanding select cntl blks*/
 ulong   d_opts;/* internal device switch control field */
};

驱动程序入口点

驱动程序入口点就是 struct devsw 结构的成员。在将设备添加到设备交换表之前,必须初始化所有这些成员。并没有强制设备驱动程序实现所有方法。未实现的成员可以被初始化为 nodev。这些入口点可以接受 dev_no(此操作被定向到的设备或子设备的设备编号)、chan(多路复用设备的通道 ID)、ext(整数,用于调用扩展子例程,如 openxreadxwritexioctlx,将额外的设备特定的参数传递给少数设备入口点。)

清单 1. 1) ddconfig 或 d_config:
 int d_config(dev_t dev_no, int cmd, struct uio *uiop)

它的调用由 sysconfig() 系统调用负责。它让设备为其第一次 open() 调用做好准备。它可以初始化、终止、请求设备的配置数据,或执行设备特定的配置函数。uio 结构包含配置信息数据区域。

清单 2. 2) ddopen 或 d_open:
int d_open(dev_t dev_no, ulong flag, chan_t chan, ext_t ext)

它支持设备操作并准备好进行数据传输。它分配内部缓冲并执行策略,基于当前的设备状态确定设备的打开方式。由 open()create() 系统调用,以及 fp_open()fp_opendev() 内核服务来调用它。输入参数标志指定打开文件控制标志,如 DREADDWRITE,等等。

清单 3. 3) ddclose 或 d_close:
int d_close(dev_t dev_no, chan_t chan)

它关闭之前打开的设备实例。它由 close() 系统调用或 fp_close() 内核服务来调用它。在 d_close() 返回给调用程序后,即使返回的是一个非零返回代码,设备实例也被认为是关闭的。

清单 4. 4) ddread 或 d_read:
 int d_read(dev_t devno, struct uio *uiop, chan_t chan, int ext)

它从一个字符设备读取数据。它由系统调用(如 read() 或 readx())和 fp_rwuio() 内核服务调用它。这里,uio 结构描述要被写入的一个或多个数据区域。

清单 5. 5) ddwrite 或 d_write:
int d_write (dev_t devno, struct uio *uiop, chan_t chan, int ext)

它将数据写入一个字符设备。它由系统调用(如 write() 或 writex())和 fp_rwuio() 内核服务调用它。这里,uio 结构描述要被写入的数据所来自的一个或多个数据区域。

清单 6. 6) ddioctl 或 d_ioctl:
int d_ioctl(dev_t devno, int cmd, void *arg, ulong devflag, chan_t chan, int ext)

它执行在 ioctl() 或 ioctlx() 系统调用或 fp_ioctl() 内核服务中所请求的特殊 I/O 控制操作。它必须响应 IOCINFO 命令,该命令返回描述设备的 devinfo 结构。

清单 7. 7) ddstrategy 或 d_strategy:
int d_strategy(struct buf* buffer)

它执行面向块的 I/O,调度对块设备的读取或写入。它将 I/O 请求映射到设备请求,从而以最少设备请求实现最大量的数据传输。缓冲区是一个指针,指向一个与 b_forw 指针链接的缓冲结构的链接列表。ddstrategy 例程可以接收含多个 buf 结构的请求。然而,不要求以任何特定顺序处理请求。该例程从不返回一个返回代码,也从不等待 I/O 完成。

清单 8. 8) ddselect 或 d_select:
int d_select(dev_t devno, ushort events, ushort *reventp,  int chan)

它检查由 events 标志指定的发生在给定设备上的一个或多个事件,并返回一个指针,指向在 reventp 中发生的事件。它的调用由 selectpoll 系统调用或 fp_select 内核服务负责。

清单 9. 9) d_mpx 或 ddmpx:
int d_mpx(dev_t devno, chan_t* chanp, char* channame)

它为多路复用设备执行逻辑信道的分配和取消分配。它在 d_open 调用之前针对设备文件的每一个 open() 调用一次,以分配一个通道,并且在 d_close 之后为设备文件的每一个关闭调用一次。只有字符类设备驱动程序支持它。chanp 是指向通道 ID 的一个指针, channame 是被分配的通道的路径名称扩展。

清单 10. 10) d_revoke 或 ddrevoke:
int d_revoke (dev_t devno, chan_t chan, int flag)

对于需要可信计算路径的设备的驱动程序,ddrevoke() 提供了一个到达终端的安全路径。只有字符类设备驱动程序支持它。它被 revoke() 系统调用或 frevoke() 内核 API 调用。

清单 11. 11) d_dump 或 dddump:
int d_dump(dev_t devno, struct uio * uiop, int cmd, int arg, chan_t chan, int ext)

它将系统转储数据写入到设备。这是设备驱动程序的一个可选例程。只有当设备驱动程序支持设备作为一个可能的内核转储的目标时才需要它。此例程不得调用可以产生页错误的任何内核服务。


设备配置例程

清单 12. 1) devswadd 内核服务:
int devswadd (dev_t devno, struct devsw *dswptr)

这在设备交换表中为 devno 指定的设备增加了一个由 dswptr 指向的设备条目。它通常由设备驱动程序的 ddconfig() 例程调用。

清单 13. 2) devswdel() 内核服务:
int devswdel(dev_t devno)

这在设备交换表中删除一个设备条目。它由 ddconfig() 例程调用,以终止设备驱动程序。


uio 结构的组件和意义

uio 结构包含一个内存缓冲,用于在实现驱动程序例程时在用户和内核空间之间的数据交换。uio 结构描述虚拟内存中不连续的缓冲。ureadcuwritecuiomoveuphysio 内核服务全都执行进入或离开 uio 结构所描述的数据缓冲的数据传输。uio 结构是在 /usr/include/sys/uio.h 文件中定义的。

struct uio {
/* ptr to array of iovec structs describing  user buffer for data transfer */
   struct  iovec *uio_iov;
/* ptr to array of xmem structs containing cross memory descriptors for iovec array.*/	
   struct  xmem  *uio_xmem;
/* #iovec elements remaining to be processed*/
   int32long64_t  uio_iovcnt;

/* #iovec elements already processed */
   int32long64_t  uio_iovdcnt;
#ifdef _LONG_LONG
/* byte offset in file/dev to read/write */
   offset_t uio_offset;    
#else /* _LONG_LONG */
#ifdef __64BIT__
/* off_t offset for ANSI-C mode */
   off_t   uio_offset;     
#else
/* ANSI-C does not support long long */
   int     uio_rsvd;
/* off_t offset for ANSI-C mode      */       
   off_t   uio_offset;     
#endif /* __64BIT__ */
#endif  /* _LONG_LONG */
/* Byte count for data transfer  */
   int32long64_t uio_resid;                
/* Type of buffer being described by uio structure. Data pointed by 
  buffer can either be in user or kernel or cross-memory region. */
   short   uio_segflg;
/* copy of file modes from open file structure */    
   long    uio_fmode;      
};

设备驱动程序示例

sample_driver.c

#include <stdio.h>
#include <syslog.h>
#include <sys/types.h> /* for dev_t and other types */
#include <sys/errno.h> /* for errno declarations */
#include <sys/sysconfig.h> /* for sysconfig() */
#include <sys/device.h> /* for devsw */
#include <sys/uio.h> /* for uiomove */
#include <sys/sysmacros.h>

struct dr_data
{
  char buffer[1024];
}dr_data[5];

int dr_open(dev_t devno, ulong devflag, chan_t chan, int ext)
{
   bsdlog(LOG_KERN|LOG_DEBUG,"Inside dr_open\n");
   return 0;
}


int dr_close (dev_t devno, chan_t chan)
{
   bsdlog(LOG_KERN|LOG_DEBUG,"Inside dr_close \n");
   return 0;
}

int dr_read (dev_t devno, struct uio *uiop, chan_t chan, int ext)
{
   uint min;
   int rc;

   min = minor_num(devno);
   rc = uiomove(dr_data[min].buffer, 1024, UIO_READ, uiop);
   bsdlog(LOG_KERN | LOG_DEBUG, "Inside dr_read min: 
   %d, buffer: %s \n", min, dr_data[min].buffer);
   return rc;
}

int dr_write (dev_t devno, struct uio *uiop, chan_t chan, int ext)
{
   uint min;
   int rc;

   min = minor_num(devno);
   rc = uiomove(dr_data[min].buffer, 1024, UIO_WRITE, uiop);
   bsdlog(LOG_KERN | LOG_DEBUG,"Inside dr_write min: 
   %d, buffer: %s \n", min, dr_data[min].buffer);
   return rc;
}

int driverdd_config (dev_t devno, int cmd, struct uio *uiop)
{
   struct devsw dswp;
   int rc = 0;

   switch (cmd)
   {
       case CFG_INIT:
            dswp.d_open     = dr_open;
            dswp.d_close    = dr_close;
            dswp.d_read     = dr_read;
            dswp.d_write    = dr_write;
            dswp.d_ioctl    = nodev;
            dswp.d_strategy = nodev;
            dswp.d_ttys     = NULL;
            dswp.d_select   = nodev;
            dswp.d_config   = driverdd_config;
            dswp.d_print    = nodev;
            dswp.d_dump     = nodev;
            dswp.d_mpx      = nodev;
            dswp.d_revoke   = nodev;
            dswp.d_dsdptr   = NULL;
            dswp.d_selptr   = NULL;
            dswp.d_opts     = DEV_MPSAFE|DEV_64BIT;



            if((rc = devswadd(devno, &dswp)) != 0)
            {
                rc = major_num(devno);
                printf("Error in devswadd: %d\n", rc);
                return rc;
            }
            break;

        case CFG_TERM:
            if((rc = devswdel(devno)) != 0)
            {
                printf("Error in devswdel: %d\n", rc);
                return rc;
            }
            break;
        default:
             printf("Invalid command \n");
            return EINVAL;
    }
    return 0;
}

给定设备驱动程序的配置应用程序示例

Config_mgr.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/sysmacros.h>
#include <sys/sysconfig.h>
#include <sys/device.h>
#include <sys/mode.h>
#include <odmi.h>
#include <cf.h>
#include <sys/cfgodm.h>
#include <sys/cfgdb.h>

int main()
{
   struct cfg_load ext_load; /* to load kernel extension */
   struct cfg_dd ddcfg; /* to invoke driver config() */
   char c, str[80];
   int rc;
   int major, minor = 0;
   dev_t devno;

   rc = odm_initialize();

   printf("\n Enter choice, (l)oad, (u)nload \n");
   while((c = getchar()) < 'a' && c > 'z');

   switch(c) {
      case 'l':
          ext_load.path = "sample_driver";
          ext_load.libpath = NULL;
          ext_load.kmid = 0;

          if(sysconfig(SYS_KLOAD,
          &ext_load, sizeof(struct cfg_load))) {
             printf("Error in loading extension\n");
             exit (1);
          }
          else
             printf("Extension Successfully loaded, kmid is %d\n", ext_load.kmid);

          major = genmajor("sample_driver");
          printf("Major number: %d\n", major);
          devno = makedev64(major, minor);
          ddcfg.kmid = ext_load.kmid;
          ddcfg.devno = devno;
          ddcfg.cmd = CFG_INIT;
          ddcfg.ddsptr = NULL;
          ddcfg.ddslen = 0;

          if (rc = sysconfig(SYS_CFGDD,
             &ddcfg, sizeof(ddcfg))) {
             printf("Error in configuring device %d %d\n", rc, errno);
             exit (1);
          }

          for(minor = 0; minor <=2; minor++) {
             devno = makedev64(major, minor);
             sprintf(str, "/dev/drvdd%d", minor);
             if (mknod(str, 0666 | _S_IFCHR, devno) == -1){
               printf("Error while creating device %s\n", str);
               exit (1);
             }
          }
          break;

    case 'u':
         ext_load.path = " sample_driver";
         ext_load.libpath = NULL;
         ext_load.kmid = 0;

         if(sysconfig(SYS_QUERYLOAD, &ext_load,
         sizeof(struct cfg_load)))
            printf("Error while querying\n");

         if(sysconfig(SYS_KULOAD, &ext_load,
         sizeof(struct cfg_load)))
            printf("Error in unloading extension\n");
         else
            printf("Extension Successfully unloaded\n");
         break;


    default:
         printf("Incorrect option\n");
         break;
 }
        return 0;
}

利用已实现设备驱动程序的应用程序的示例

application.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
  int fd0, fd1;
  char rbuf[1024];

  fd0 = open("/dev/drvdd0", O_RDWR);
  if(fd0 < 0){
    printf("Error in opening /dev/drvdd0\n");
    return -1;
  }
  fd1 = open("/dev/drvdd1", O_RDWR);
  if(fd1 < 0){
    printf("Error in opening /dev/drvdd1\n");
    return -1;
  }
  write(fd0, "Hello", 5);
  write(fd1, "World", 5);

  read(fd0, rbuf, 5);
  printf("Read from /dev/drvdd0 : %s\n",rbuf);

  read(fd1, rbuf, 5);
  printf("Read from /dev/drvdd1 : %s\n",rbuf);
  return 0;
}

Makefile

all: sample_driver config_mgr application

config_mgr: config_mgr.c
          cc -q64 -o config_mgr -g config_mgr.c -lodm -lcfg

application: application.c
	  cc -o application application.c

K_LIBS= -bI:/usr/lib/kernex.exp -lsys -lcsys

sample_driver: sample_driver.c
	  cc -q64 -o sample_driver64.o -c sample_driver.c -D_KERNEL -D_64BIT_KERNEL
	  ld -b64 -o sample_driver sample_driver64.o -e driverdd_config $(K_LIBS)
        
clean:
	  rm -f *.o sample_driver sample_driver32 
        sample_driver64 config_mgr application  2> /dev/null

样例驱动程序的编译和测试

以 root 权限登录到 AIX 6.1 或更高版本的系统。将上述文件复制到开发目录,并在 shell 命令提示符下运行 make 命令。

此操作构建必要的驱动程序、配置实用程序和一个应用程序,该应用程序又会调用驱动程序 API。然后在命令提示符下运行 ./config_mgr 实用程序,并检查驱动程序是否已被成功加载。如果没有抛出错误,您可以继续执行 ./application,它将测试驱动程序 API。

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球网站上的 英文原文
  • AIX 6.1 内核扩展和设备支持编程
  • AIX 内核技术参考 1
  • AIX 内核技术参考 2
  • AIX and UNIX 专区:developerWorks 的“AIX and UNIX 专区”提供了大量与 AIX 系统管理的所有方面相关的信息,您可以利用它们来扩展自己的 UNIX 技能。
  • AIX and UNIX 新手入门:访问“AIX and UNIX 新手入门”页面可了解更多关于 AIX 和 UNIX 的内容。
  • AIX and UNIX 专题汇总:AIX and UNIX 专区已经为您推出了很多的技术专题,为您总结了很多热门的知识点。我们在后面还会继续推出很多相关的热门专题给您,为了方便您的访问,我们在这里为您把本专区的所有专题进行汇总,让您更方便的找到您需要的内容。
  • AIX and UNIX 下载中心:在这里你可以下载到可以运行在 AIX 或者是 UNIX 系统上的 IBM 服务器软件以及工具,让您可以提前免费试用他们的强大功能。
  • IBM Systems Magazine for AIX 中文版:本杂志的内容更加关注于趋势和企业级架构应用方面的内容,同时对于新兴的技术、产品、应用方式等也有很深入的探讨。IBM Systems Magazine 的内容都是由十分资深的业内人士撰写的,包括 IBM 的合作伙伴、IBM 的主机工程师以及高级管理人员。所以,从这些内容中,您可以了解到更高层次的应用理念,让您在选择和应用 IBM 系统时有一个更好的认识。

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX, Linux
ArticleID=933006
ArticleTitle=IBM AIX 设备驱动程序开发
publish-date=06132013