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

s3c2410 linux LCD驱动程序分析

2011-08-25 15:24 309 查看
//*******************************************************

//* 2007.6.18

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

在/kernel/include/asm-arm/arch-s3c2410/bitfield.h 文件中:

#ifndef __ASSEMBLY__

#define UData(Data) ((unsigned long) (Data))

#else

#define UData(Data) (Data)

#endif
例:UData(5); = 5
/*

* MACRO: Fld

*

* Purpose

*     The macro "Fld" encodes a bit field, given its size and its shift value

*     with respect to bit 0.

*

* Note

*     A more intuitive way to encode bit fields would have been to use their

*     mask. However, extracting size and shift value information from a bit

*     field\'s mask is cumbersome and might break the assembler (255-character

*     line-size limit).

*

* Input

*     Size       Size of the bit field, in number of bits.

*     Shft       Shift value of the bit field with respect to bit 0.

*

* Output

*     Fld         Encoded bit field.

*/
#define Fld(Size, Shft) (((Size) << 16) (Shft))
例:Fld(2,5); = 0x20005
/*

* MACROS: FSize, FShft, FMsk, FAlnMsk, F1stBit

*

* Purpose

*     The macros "FSize", "FShft", "FMsk", "FAlnMsk", and "F1stBit" return

*     the size, shift value, mask, aligned mask, and first bit of a

*     bit field.

*

* Input

*     Field       Encoded bit field (using the macro "Fld").

*

* Output

*     FSize       Size of the bit field, in number of bits.

*     FShft       Shift value of the bit field with respect to bit 0.

*     FMsk       Mask for the bit field.

*     FAlnMsk     Mask for the bit field, aligned on bit 0.

*     F1stBit     First bit of the bit field.

*/
#define FSize(Field) ((Field) >> 16)
例:FSize(0x20005); = 2
#define FShft(Field) ((Field) & 0x0000FFFF)
例:FShft(0x20005); = 5
/*

* MACRO: FInsrt

*

* Purpose

*     The macro "FInsrt" inserts a value into a bit field by shifting the

*     former appropriately.

*

* Input

*     Value       Bit-field value.

*     Field       Encoded bit field (using the macro "Fld").

*

* Output

*     FInsrt     Bit-field value positioned appropriately.

*/

#define FInsrt(Value, Field) \\

                 (UData (Value) << FShft (Field))
例:FInsrt(0x3, 0x20005); = 0x3 << 0x0005 = 0x60
------------------------------------------------------------------------

在/kernel/include/asm-arm/arch-s3c2410/hardware.h 文件中:

/*

* S3C2410 internal I/O mappings

*

* We have the following mapping:

*   phys   virt

*   48000000 e8000000

*/
#define VIO_BASE   0xe8000000 /* virtual start of IO space */

#define PIO_START   0x48000000 /* physical start of IO space */
#define io_p2v(x) ((x) | 0xa0000000)

#define io_v2p(x) ((x) & ~0xa0000000)
# define __REG(x) io_p2v(x)

# define __PREG(x) io_v2p(x)
     这里,在实际的寄存器操作中,都用__REG(x) 宏将物理地址转换为了虚拟地址,然后再对这些虚拟地址进行读写操作。
------------------------------------------------------------------------

     当应用程序对设备文件进行ioctl操作时候会调用它们。对于fb_get_fix(),应用程序传入的是fb_fix_screeninfo结构,在函数中对其成员变量赋值,主要是smem_start(缓冲区起始地址)和smem_len(缓冲区长度),最终返回给应用程序。
在/kernel/drivers/video/s3c2410fb.c 文件中的s3c2410fb_map_video_memory 函数中:

fbi->fb.fix.smem_len = fbi->max_xres * fbi->max_yres *

       fbi->max_bpp / 8;
fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len PAGE_SIZE);

fbi->map_cpu = consistent_alloc(GFP_KERNEL, fbi->map_size,

             &fbi->map_dma);

if (fbi->map_cpu)

{

   fbi->screen_cpu = fbi->map_cpu PAGE_SIZE;

   fbi->screen_dma = fbi->map_dma PAGE_SIZE;

   fbi->fb.fix.smem_start = fbi->screen_dma;

}

在/kernel/include/asm-arm/proc-armo/page.h 文件中:

/* PAGE_SHIFT determines the page size.   This is configurable. */

#if defined(CONFIG_PAGESIZE_16)

#define PAGE_SHIFT 14   /* 16K */

#else   /* default */

#define PAGE_SHIFT 15   /* 32K */

#endif
在/kernel/include/asm-arm/page.h 文件中:

#define PAGE_SIZE       (1UL << PAGE_SHIFT)

#define PAGE_MASK       (~(PAGE_SIZE-1))
/* to align the pointer to the (next) page boundary */

#define PAGE_ALIGN(addr) (((addr) PAGE_SIZE-1)&PAGE_MASK)
在/kernel/arch/arm/mm/consistent.c 文件中:

/*

* This allocates one page of cache-coherent memory space and returns

* both the virtual and a "dma" address to that space.   It is not clear

* whether this could be called from an interrupt context or not.   For

* now, we expressly forbid it, especially as some of the stuff we do

* here is not interrupt context safe.

*

* Note that this does *not* zero the allocated area!

*/

void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle)

     这里首先计算出需要视频缓冲区的大小(LCD屏的宽度 * LCD屏的高度 * 每像素的位数 / 每字节的位数)

fbi->fb.fix.smem_len = 240*320*16/8 = 0x25800 =150K(9.375个PAGE)

PAGE_SHIFT = 14

PAGE_SIZE = 1<<14 = 0x4000 = 16K (1个PAGE)

PAGE_MASK = 0xFFFFC000

fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len PAGE_SIZE) = PAGE_ALIGN(150K 16K) = PAGE_ALIGN(166K)

= (166K 16K - 1) & 0xFFFFC000 = 0x2D7FF & 0xFFFFC000 = 0x2C000 =176K

consistent_alloc(GFP_KERNEL, 176K, &fbi->map_dma);

最后得到:
       framebuffer(物理地址)

        |---------------|

        |       ...     |

-------|---------------| <-- fbi->map_dma

        |       16K     |

分配了|---------------| <-- fbi->screen_dma = fbi->fb.fix.smem_start

   176K|              |

共11个|             |   160K = 10个PAGE

PAGE |      160K   |   可以容下所需的150K 视频缓冲区大小

(16K) |               |

          |               |

-------|---------------|-------

        |       ...     |

       |---------------|

--   作者:asan-xu

--   发布时间:2007-10-11 8:49:10

--  

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

//* 2007.6.19

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

在/kernel/drivers/video/s3c2410fb.c 文件中的s3c2410fb_activate_var 函数中:

unsigned long VideoPhysicalTemp = fbi->screen_dma;

     这里已经得到了framebuffer 在内存中的起始地址为VideoPhysicalTemp,地址数据位为A[30:0]。
new_regs.lcdcon1 = fbi->reg.lcdcon1 & ~LCD1_ENVID;
new_regs.lcdcon2 = (fbi->reg.lcdcon2 & ~LCD2_LINEVAL_MSK)

       | LCD2_LINEVAL(var->yres - 1);
/* TFT LCD only ! */

new_regs.lcdcon3 = (fbi->reg.lcdcon3 & ~LCD3_HOZVAL_MSK)

       | LCD3_HOZVAL(var->xres - 1);
new_regs.lcdcon4 = fbi->reg.lcdcon4;

new_regs.lcdcon5 = fbi->reg.lcdcon5;
   LCDCON1 首先需要禁止视频输出才能进行寄存器的设置,然后对LCDCON2,LCDCON3 进行设置,主要是增加LINEVAL 和HOZVAL 这两个显示尺寸的参数。LCDCON4,LCDCON5 按原来配置设置。

LCDBANK[29:21]   为系统内存中视频缓冲区在系统存储器内的段地址的A[30:22]

LCDBASEU[20:0]   为LCD framebuffer 的起始地址的A[21:1]

LCDBASEL[20:0]   为LCD framebuffer 的结束地址的A[21:1]

OFFSIZE[21:11] 为某一行的第一个半字与前一行最后一个半字之间的距离(单位:半字数,即2个字节)

PAGEWIDTH[10:0] 为显示存储区的可见帧宽度(单位:半字数,即2个字节)

new_regs.lcdsaddr1 =

   LCDADDR_BANK(((unsigned long)VideoPhysicalTemp >> 22))

   | LCDADDR_BASEU(((unsigned long)VideoPhysicalTemp >> 1));
new_regs.lcdsaddr2 = LCDADDR_BASEL(

   ((unsigned long)VideoPhysicalTemp (var->xres * 2 * (var>yres))) >> 1);
     这里LCDADDR_BASEL 的计算方法为用framebuffer 在内存中的起始地址VideoPhysicalTemp,加上framebuffer 的大小(LCD屏的宽度 * LCD屏的高度 * 每像素的位数 / 每字节的位数),得到framebuffer 在内存中的结束地址,然后右移1位。
new_regs.lcdsaddr3 = LCDADDR_OFFSET(0) | (LCDADDR_PAGE(var->xres));
     这里PAGEWIDTH 的计算方法为:LCD屏的宽度*每像素的位数/16位 (半字)。

问题:以上这些操作是否已经对DMA 控制器进行了设置?

     我认为这里将framebuffer 在内存中的起始地址为VideoPhysicalTemp 变换后载入LCDADDR1,LCDADDR2,LCDADDR3 中就已经完成了对LCDCDMA 控制器的源数据的基地址设置,当打开LCDCON1 |= LCD1_ENVID; 后就可以由LCDCDMA 控制器自动从framebuffer 中传数据到LCD 屏幕了。

------------------------------------------------------------------------

在/kernel/drivers/video/s3c2410fb.c 文件中的xxx_stn_info 结构体初始化中:

lcdcon5 : LCD5_FRM565 | LCD5_INVVLINE | LCD5_INVVFRAME | LCD5_HWSWP | LCD5_PWREN,
     INVVCLK , INVLINE , INVFRAME , INVVD :通过前面的时序图,我们知道,CPU的LCD控制器输出的时序默认是正脉冲,而LCD需要VSYNC(VFRAME)、VLINE(HSYNC)均为负脉冲,因此 INVLINE 和 INVFRAME 必须设为“1 ”,即选择反相输出。 INVVDEN , INVPWREN , INVLEND 的功能同前面的类似。
     PWREN 为LCD电源使能控制。在CPU LCD控制器的输出信号中,有一个电源使能管脚LCD_PWREN,用来做为LCD屏电源的开关信号。
     其中LCD5_HWSWP   一项,设置了LCD从内存中显示数据时,经过了半字交换。
16BPP Display

(BSWP = 0, HWSWP = 0)

D[31:16] D[15:0]

000H P1   P2

004H P3   P4

008H P5   P6

...
(BSWP = 0, HWSWP = 1)

D[31:16] D[15:0]

000H   P2   P1

004H   P4   P3

008H   P6   P5

...
像素显示顺序如下:

P1 P2 P3 P4 P5 ...
例如:内存地址的数据为:0x11223344 (32位)

     系统存储器采用Big-Endian(大端模式)存储格式,地址数据格式如下:

D[31:16]   D[15:0]

00   01   02   03

00H     0x11 0x22 0x33 0x44

         (0x1122)   (0x3344)

04H ...

08H ...
则首先显示0x3344 的数据到第一个像素,然后再显示0x1122 到第二个像素。
     系统存储器采用Little-Endian(小端模式)存储格式,地址数据格式如下:

D[31:16]   D[15:0]

03   02   01   00

00H     0x11 0x22 0x33 0x44

         (0x1122)   (0x3344)

04H ...

08H ...
则首先显示0x3344 的数据到第一个像素,然后再显示0x1122 到第二个像素。

--   作者:asan-xu

--   发布时间:2007-10-11 8:49:58

--  
//*******************************************************

//* 2007.6.20

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

在/kernel/arch/arm/mm/consistent.c 文件中的consistent_alloc 函数中:

void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle)

{

   ...

   virt = page_address(page);

   *dma_handle = virt_to_bus(virt);

   ret = __ioremap(virt_to_phys(virt), size, 0);

   ...

}
     这里调用该函数来分配一段内存空间有两个返回值,一个返回值返回给了ret 指针,另一个返回值返回给了dma_handle 指针。virt_to_bus 和virt_to_phys 函数的调用可以参考下面的分析。经过分析这两个函数作用一样,都是将virt 这个虚拟地址转换为物理地址。所以返回给指针dma_handle 的是所分配内存的起始地址(物理地址)。__ioremap 函数的调用也可以参考下面的说明,该函数也返回所分配内存的起始地址(虚拟地址),不过是经过I/O
内存映射的,把物理地址转换为了虚拟地址。
     这样一来就很清楚了,返回的framebuffer 的物理地址给了指针dma_handle,也就是fbi->map_dma,到fbi->screen_dma,再到fbi->fb.fix.smem_start,最后到了指针VideoPhysicalTemp,这样写入到LCDADDR1,LCDADDR2 寄存器中的framebuffer 的地址其实都是物理地址。
     而返回的framebuffer 的虚拟地址给了指针ret,也就是fbi->map_cpu,到fbi->screen_cpu,最后到了display->screen_base(见/kernel/drivers/video/s3c2410fb.c 文件中的s3c2410fb_set_var 函数)。

------------------------------------------------------------------------

在/kernel/drivers/video/fbmem.c 文件中:

/**

* register_framebuffer - registers a frame buffer device

* @fb_info: frame buffer info structure

*

* Registers a frame buffer device @fb_info.

*

* Returns negative errno on error, or zero for success.

*

*/

int

register_framebuffer(struct fb_info *fb_info)

/**

* unregister_framebuffer - releases a frame buffer device

* @fb_info: frame buffer info structure

*

* Unregisters a frame buffer device @fb_info.

*

* Returns negative errno on error, or zero for success.

*

*/

int

unregister_framebuffer(struct fb_info *fb_info)

static int

fb_open(struct inode *inode, struct file *file)

static int

fb_release(struct inode *inode, struct file *file)

static ssize_t

fb_read(struct file *file, char *buf, size_t count, loff_t *ppos)

static ssize_t

fb_write(struct file *file, const char *buf, size_t count, loff_t *ppos)

static int

fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,

unsigned long arg)

static int

fb_mmap(struct file *file, struct vm_area_struct * vma)

在该文件中包含了所有驱动LCD 的函数.在fb_read 和fb_write 这两个函数中,都对framebuffer 进行了操作.

fb_read 函数中:

char *base_addr;

base_addr = info->disp->screen_base;

count -= copy_to_user(buf, base_addr+p, count);

fb_write 函数中:

char *base_addr;

base_addr = info->disp->screen_base;

count -= copy_from_user(base_addr+p, buf, count);

所读写的framebuffer 的基地址就是disp->screen_base,也就是fbi->screen_cpu 所指的framebuffer 的虚拟地址.

从而得到:

framebuffer(虚拟地址)

|---------------|

| ... |

-------|---------------| map_cpu

| 16K |

分配了 |---------------| screen_cpu = display->screen_base

176K | |

共11个 | | 160K = 10个PAGE

PAGE | 160K | 可以容下所需的150K 视频缓冲区大小

(16K) | |

| |

-------|---------------|-------

| ... |

|---------------|

其中display->screen_base 结构在/kernel/include/video/fbcon.h 文件中定义.

得出结论,在分配framebuffer 时一共返回两个指针,虽然是同一块内存空间,但一个返回的是实际的物理地址,另一个返回的是经过地址转换的虚拟地址.在设置LCD 控制器中framebuffer 起始地址寄存器时,用的是所分配内存的物理地址;而当要对framebuffer 进行读写操作时,用的是同一块内存的物理地址所转换后的虚拟地址.由此可以知道,内核在对每个I/O 地址进行读写操作时用的都是经过转换的虚拟地址.

------------------------------------------------------------------------

------------------------------------------------------------------------

在/kernel/include/asm-arm/arch-s3c2410/memory.h 文件中:

/*

* Page offset: 3GB

*/

#define PAGE_OFFSET (0xc0000000UL)

#define PHYS_OFFSET (0x30000000UL)

/*

* We take advantage of the fact that physical and virtual address can be the

* saem. Thu NUMA code is handling the large holes that might exist between

* all memory banks.

*/

#define __virt_to_phys__is_a_macro

#define __phys_to_virt__is_a_macro

#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)

#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)

由此可见: 起始点地址 PHYS_OFFSET PAGE_OFFSET

| |

|--0x30000000--|------间隔------|

PHYS_OFFSET(物理地址起始点): |--------------|...........................

|

|-----------0xc0000000----------|

PAGE_OFFSET(虚拟地址起始点): |-------------------------------|..........

物理地址与虚拟地址的间隔为:PAGE_OFFSET - PHYS_OFFSET.这样一来,以上对物理地址和虚拟地址之间转换的宏定义就很好理解了.

虚拟地址转为物理地址宏:__virt_to_phys(x)

= (x) - (PAGE_OFFSET - PHYS_OFFSET) = (x) - PAGE_OFFSET + PHYS_OFFSET

物理地址转为虚拟地址宏:__phys_to_virt(x)

= (x) + (PAGE_OFFSET - PHYS_OFFSET) = (x) + PAGE_OFFSET - PHYS_OFFSET

内核虚拟地址和实际物理地址仅仅是相差一个偏移量(PAGE_OFFSET),可以很方便的将其转化为物理内存地址,同时内核也提供了virt_to_phys() 函数将内核虚拟空间中的物理影射区地址转化为物理地址.

/*

* Virtual view DMA view memory address translations

* virt_to_bus: Used to translate the virtual address to an

* address suitable to be passed to set_dma_addr

* bus_to_virt: Used to convert an address for DMA operations

* to an address that the kernel can use.

*/

#define __virt_to_bus__is_a_macro

#define __bus_to_virt__is_a_macro

#define __virt_to_bus(x) __virt_to_phys(x)

#define __bus_to_virt(x) __phys_to_virt(x)

这里注意:__virt_to_bus(x) 就等于__virt_to_phys(x)

------------------------------------------------------------------------

在/kernel/include/asm-arm/memory.h 文件中:

/*

* These are *only* valid on the kernel direct mapped RAM memory.

*/

static inline unsigned long virt_to_phys(volatile void *x)

{

return __virt_to_phys((unsigned long)(x));

}

/*

* Virtual DMA view memory address translations

* Again, these are *only* valid on the kernel direct mapped RAM

* memory.

*/

#define virt_to_bus(x) (__virt_to_bus((unsigned long)(x)))

由上面的分析可知:virt_to_bus(x) 和virt_to_phys(volatile void *x) 这两个函数调用的都是__virt_to_phys(x) 即((x) - PAGE_OFFSET + PHYS_OFFSET).所以这两个调用都是将虚拟地址转换为了物理地址.

------------------------------------------------------------------------

在/kernel/arch/arm/mm/ioremap.c 文件中:

/*

* Remap an arbitrary physical address space into the kernel virtual

* address space. Needed when the kernel wants to access high addresses

* directly.

*

* NOTE! We need to allow non-page-aligned mappings too: we will obviously

* have to convert them into an offset in a page-aligned mapping, but the

* caller shouldn't need to know that small detail.

*

* 'flags' are the extra L_PTE_ flags that you want to specify for this

* mapping. See include/asm-arm/proc-armv/pgtable.h for more information.

*/

void * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags)

ioremap 函数的作用是将physical address以及bus address映射为kernel的

virtual adrress.

ioremap 的作用是把I/O 内存地址(物理地址)映射到虚拟地址空间,使用之前需要分配I/O 内存区域.但是,ioremap 函数的内部实现并不是简单的((IO的物理地址)-0x10000000 +0xE4000000).所以,得到的虚拟地址可能是不同的.

因为linux 用的是页面映射机制,CPU 不能按物理地址来访问存储空间,而必须使用虚拟地址,所以必须反向的从物理地址出发找到一片虚存空间并建立起映射.

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

//* 2007.6.22

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

在/kernel/drivers/video/fbmem.c 文件中:

static struct file_operations fb_fops = {

owner: THIS_MODULE,

read: fb_read,

write: fb_write,

ioctl: fb_ioctl,

mmap: fb_mmap,

open: fb_open,

release: fb_release,

#ifdef HAVE_ARCH_FB_UNMAPPED_AREA

get_unmapped_area: get_fb_unmapped_area,

#endif

};

这个结构中定义了对LCD 所分配的framebuffer 进行读写操作,以及一些内存映射之类的函数,这些源函数都在该文件中.

/**

* register_framebuffer - registers a frame buffer device

* @fb_info: frame buffer info structure

*

* Registers a frame buffer device @fb_info.

*

* Returns negative errno on error, or zero for success.

*

*/

int

register_framebuffer(struct fb_info *fb_info)

这个是对LCD 的framebuffer 设备进行注册时调用到的注册函数,该函数在/kernel/drivers/video/s3c2410fb.c 文件的s3c2410fb_init 函数里的最后部分被调用.

fb_info->devfs_handle =

devfs_register (devfs_handle, name_buf, DEVFS_FL_DEFAULT,

FB_MAJOR, i, S_IFCHR | S_IRUGO | S_IWUGO,

&fb_fops, NULL);

在注册函数的最后部分,将前面的fb_fops 结构里的各个驱动函数的入口点传入到devfs_register()设备注册函数中.

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

//* 2007.6.26

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

上面这个devfs_register 函数(原型在/kernel/fs/devfs/base.c 文件中)执行前,在fbmem_init 函数中调用了函数:

devfs_handle = devfs_mk_dir (NULL, "fb", NULL);

在/kernel/fs/devfs/base.c 文件中:

/**

* devfs_mk_dir - Create a directory in the devfs namespace.

* @dir: The handle to the parent devfs directory entry. If this is %NULL the

* new name is relative to the root of the devfs.

* @name: The name of the entry.

* @info: An arbitrary pointer which will be associated with the entry.

*

* Use of this function is optional. The devfs_register() function

* will automatically create intermediate directories as needed. This function

* is provided for efficiency reasons, as it provides a handle to a directory.

* Returns a handle which may later be used in a call to devfs_unregister().

* On failure %NULL is returned.

*/

devfs_handle_t devfs_mk_dir (devfs_handle_t dir, const char *name, void *info)

这个devfs_mk_dir 函数会在设备文件系统中创建一个名为fb 的目录,并返回一个带有devfs 设备文件系统目录结构的数据结构变量devfs_handle.然后把这个数据结构作为下一步调用devfs_register 函数时的参数,该参数在调用设备文件系统注册清除函数devfs_unregister 时也要作为参数传入.

/**

* devfs_register - Register a device entry.

* @dir: The handle to the parent devfs directory entry. If this is %NULL the

* new name is relative to the root of the devfs.

* @name: The name of the entry.

* @flags: A set of bitwise-ORed flags (DEVFS_FL_*).

* @major: The major number. Not needed for regular files.

* @minor: The minor number. Not needed for regular files.

* @mode: The default file mode.

* @ops: The &file_operations or &block_device_operations structure.

* This must not be externally deallocated.

* @info: An arbitrary pointer which will be written to the @private_data

* field of the &file structure passed to the device driver. You can set

* this to whatever you like, and change it once the file is opened (the next

* file opened will not see this change).

*

* Returns a handle which may later be used in a call to devfs_unregister().

* On failure %NULL is returned.

*/

devfs_handle_t devfs_register (devfs_handle_t dir, const char *name,

unsigned int flags,

unsigned int major, unsigned int minor,

umode_t mode, void *ops, void *info)

函数devfs_register 是设备文件系统的主册函数,会在刚才创建的目录下再创建一个名为name 的设备文件节点.返回的devfs_handle_t 数据结构变量会在调用设备文件系统注册清除函数devfs_unregister 时作为参数传入.

fb_info->devfs_handle =

devfs_register (devfs_handle, name_buf, DEVFS_FL_DEFAULT,

FB_MAJOR, i, S_IFCHR | S_IRUGO | S_IWUGO,

&fb_fops, NULL);

调用该函数后,会在刚才创建的fb 目录下再创建一个名为0 (name_buf)的设备文件节点,并赋予相应的权限,以及主次设备号和file_operations 结构的函数入口.

这样一来,Linux 设备文件的创建,删除和目录层次等都由各设备驱动程序管理,再也不用手工创建设备文件节点了,再也不需要mknod 时查找对应的主设备号了,也不用依靠复杂的脚本来管理设备文件了.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息