2.3 串口驱动程序函数库分析
为了进一步加深对IO设备管理数据结构的理解,本节将分别对函数库ioLib、iosLib进行分析。
2.3.1 函数库ioLib
ioLib库为上层提供了7个基本的函数接口:creat(), remove(), open(), close(), read(), write()以及ioctl()。上层用户只需要对这7个函数进行操作就能够完成对硬件的访问。下面一次分析ioLib库中各个函数库的功能。
1. int creat
(
const char *name,
int flag
)
该函数提供了一个与设备无关的通用接口,用于创建一个文件(可以是普通的磁盘文件也可以是抽象的设备文件),该文件的路径为name,创建完毕后自动打开,打开的参数为flag。该函数的正常返回值为文件描述符,否则将会返回ERROR。
对照图2.8,该函数根据设备的名称,从链表中找到该设备对应的设备号drmNum(该数值即为函数返回的文件描述符),然后以此为下标就可以找到该类设备的de_create函数指针,从而找到该设备的create函数。
2. int open
(
const char *name,
int flags,
int mode
)
该函数打开一个文件以方便进行读、写或者更新,打开后返回该文件的文件描述符。open()函数的参数为文件名以及访问方式:
- l O_RDONLY (0) 以只读方式打开
- l O_WRONLY (1) 以只写方式打开
- l O_RDWR (2) 以读写方式打开
- l O_CREAT (0x0200)如果文件不存在,就创建一个文件并打开。
3. LOCAL int ioCreateOrOpen
(
const char *name,
int flags,
int mode,
BOOL create
)
这个函数其实是函数create()和open()函数的实现主体。create()和open()函数只是根据参数create简单调用函数ioCreateOrOpen()而已,真正的实现在函数ioCreateOrOpen()中。
4. STATUS unlink
(
char *name
)
该函数主要是和posix兼容,它的功能与remove完全相同。
5. STATUS remove
(
const char *name
)
调用函数iosDelete来删除文件。如果有符号链接,则需要沿着符号链接直接找到文件并删除。
6. STATUS close
(
int fd
)
调用函数ioClose函数关闭文件。
7. int rename
(
const char *oldname,
const char *newname
)
修改文件名。
并不是所有的设备都支持重命名操作,比如通常的dosFS和rt11FS都是支持重命名操作的,而netDrv和nfsDrv则不支持,因此在使用这个函数前需要明确设备是否支持重命名操作。
重命名操作主要通过调用函数ioctl (fd, FIORENAME, (int) newname)完成。
注意:调用函数ioctl (fd, FIORENAME, (int) newname)重命名前需要先打开文件open()。
8. int read
(
int fd,
char *buffer,
size_t maxbytes
)
调用函数iosRead实现读操作,maxbytes为读取的最大字节数,读取之后存放在buffer指定的地址空间,不过可能由于文件中字节数的限制等因素,实际读取的字节数可能会小于maxbytes,因此需要在最终的返回值中返回实际读取的字节数。
9. int write
(
int fd,
char *buffer,
size_t nbytes
)
调用函数iosWrite向指定的文件中写入数据,nbytes为期望写入的字节数,但是实际上可能会由于文件本身的限制导致实际写入的字节数小于nbytes,因此需要在返回值中记录实际写入的字节数。
10. int ioctl
(
int fd,
int function,
int arg
)
直接调用函数iosIoctl实现对文件的控制操作。
11. int lseek
(
int fd,
long offset,
int whence
)
设定一个文件的读写指针,下次读写操作将从设定的位置开始。
参数whence有三个数值:
- l SEEK_SET (0) 设定到相对于文件起始位置偏移offset位置。
- l SEEK_CUR (1) 设定到当前位置偏移offset的位置。
- l SEEK_END (2) 相对于文件结束位置偏移offset的位置。
注意:如果指定的地址是无效的地址(超出了文件的范围),那么将返回错误指示。
12. int readv
(
int fd,
struct iovec *iov,
int iovcnt
)
从设备fd中读取数据,保存在iov起始地址为iov的数组中。iovcnt为结构iovec数组iov的元素的个数。iov是一个数组指针,它指向一个iovec结构数组,iovec结构中的元素iov_base指定了数据保存起始位置地址,而iovcnt则指定了iov数组的元素个数,因此总的读出的字节数为各个数组元素中保存数据的字节数之和。如图2.9所示。
13. int writev
(
int fd,
register struct iovec *iov,
int iovcnt
)
该函数调用iosWrite函数将几段分散的数据写入到fd设备中。
14. STATUS ioFullFileNameGet
(
char * pathName,
DEV_HDR ** ppDevHdr,
char * fullFileName
)
通常来说,一个完整的文件的路径包括两部分,该文件所在的设备路径,以及该文件在该设备上的相对路径,这两部分组合得到了一个完整的路径。这个函数的作用就是根据参数pathName制定的路径名,这个路径名可以是相对(相对麽默认的路径ioDefPath)路径名或者绝对路径名。函数ioFullFileNameGet的作用个就是根据提供的路径名找到该文件坐在的设备及其相对于该设备的路径。其中设备名结构变量指针的指针保存在DEV_HDR ** ppDevHdr参数中返回,相对于设备的文件名则存放在参数char * fullFileName中保存。
举例来说当前默认路径为“/dev/sd1/x/”,相对路径名为“y.txt”,该函数将会找到设备“/dev/sd1/”对应的DEV_HDR ** ppDevHdr指针返回,并将fullFileName设置为"x/y.txt"返回。
15. STATUS ioDefPathSet
(
char *name
)
设定默认路径,并将其复制到全局变量ioDefPath中,注意name必须是一个绝对路径,也就是说name指定的路径必须包含一个有效的设备(即从iosDevList中能够搜索到的设备)。
16. void ioDefPathGet
(
char *pathname
)
将ioDefPath复制到pathname中返回。
17. STATUS chdir
(
char *pathname
)
这个函数等价于函数ioDefPathSet(),可以认为是ioDefPathSet()函数的一个别名。
18. char *getcwd
(
char *buffer,
int size
)
ioDefPathGet()函数的别名。只是如果参数size指定的空间太小的话将返回ERROR。
19. char *getwd
(
char *pathname
)
ioDefPathGet()函数的别名。
20. STATUS ioDefPathCat
(
char *name
)
修改默认的路径。如果name是一个包含设备路径的一个绝对路径名,那么它就成为了一个新的默认路径。否则name将会被连接到当前默认路径从而更新了新的默认路径。
例如:如果默认路径为"sda/x",name="sda/y",则默认路径修改为"sda/y";如果name="/y",则默认路径修改为"sda/x/y".
21. void ioDefDevGet
(
char *devName
)
获取默认路径所属的设备的名称。
22. void ioDefDirGet
(
char *dirName
)
这个函数在新的版本中将会删除。它的主要作用就是获取默认路径相对于所属设备的相对路径。
如设备名为"sda",默认路径为"sda/x",则ioDefDevGet()函数获取了"sda"设备,而函数ioDefDirGet()则获取了相对路径。
23. void ioGlobalStdSet
(
int stdFd,
int newFd
)
文件ioLib中定义了一个数组LOCAL int ioStdFd [3],该数组表明标准文件描述符。这三个标准的文件描述符主要是:标准输入0、标准输出1、错误输出2。这个函数的作用,就是设置这个数组中某个元素的文件描述符。
stdFd:指的是ioStdFd数组中元素的下标,0~2有效。
newFd:数组中元素的数值。
24. int ioGlobalStdGet
(
int stdFd
)
获取数组ioStdFd中某个元素的数值,即文件描述符的数值。
25. void ioTaskStdSet
(
int taskId,
int stdFd,
int newFd
)
对VxWorks系统来说,并不是每个任务的标准输入、标准输出、错误输出都是相同的,多数任务的标准输入是键盘,标准输出是显示器等,但是对某些串口监控的任务来说,可以把它的标准输入设置为串口等等。
每个任务描述符中都有一个数组taskStd,表明这个任务的标准输入、标准输出以及错误输出,这个函数的功能,就是设置一个任务的标准输入、标准输出、错误输出。
26. int ioTaskStdGet
(
int taskId,
int stdFd
)
获取一个任务的标准输入、标准输出、错误输出
27. BOOL isatty
(
int fd /* file descriptor to check */
)
判断一个文件描述符是否是一个tty。最初的计算机没有键盘和显示器,输入输出都比较麻烦,后来有Teletype公司生产了一种字符型终端设备,因此有很多计算机操作系统都把字符型终端设备叫做tty设备。
关于文件路径的进一步详细操作可以参考函数库pathLib。
2.3.2 函数库iosLib
函数库iosLib是库ioLib的底层实现,它负责将各个硬件设备及其驱动组织起来,从而使得用户只需要调用ioLib库就可以通过类似于文件IO的访问方式来访问各个硬件设备。在对iosLib库函数进行分析的过程中我们将主要分析系统是如何将这些设备以及驱动组织起来,以及如何为ioLib库提供接口的。
在函数库iosLib中定义了一个通用的接口结构DRV_ENTRY,它为ioLib库提供了一个通用过的接口,使得ioLib无需了解具体的设备类型就可以对硬件设备进行访问;另一方面它又通过继承的方法,将不同类型的设备保存在链表中进行统一管理(参见图2.8)。下面对设备的管理函数进行详细分析。
1. static STATUS nullWrite
(
int dummy,
char * pBuf,
int nBytes
)
空函数。
2. STATUS iosInit
(
int max_drivers,
int max_files,
char *nullDevName
)
iosLib函数库初始化,该函数的主要作用是动态创建了一个FD_ENTRY结构数组和一个DRV_ENTRY结构数组,并进行了初始化。
3. int iosDrvInstall
(
FUNCPTR pCreate,
FUNCPTR pDelete,
FUNCPTR pOpen,
FUNCPTR pClose,
FUNCPTR pRead,
FUNCPTR pWrite,
FUNCPTR pIoctl
)
从DRV_ENTRY结构数组中找到一个空闲的DRV_ENTRY结构变量,将参数指定函数指针填充到该。注意由于最开始的一个元素即drvTable[0]中填充的是一个空设备,因此查找空闲的元素是从下表1开始查找的。如图2.10。该函数返回下标i。
图2.10 函数iosDrvInstall的操作
正是这个步骤才使得一类设备的特殊访问方法与通用的接口连接起来。
4. STATUS iosDrvRemove
(
int drvnum,
BOOL forceClose
)
函数iosDrvInstall的逆过程,将DRV_ENTRY结构数组中的下表为drvnum的元素清空。如果forceClose参数为TRUE则需要强制关闭该设备已经打开的文件。如果forceClose为FALSE但是该设备有打开的文件,则返回错误。
注意:一个设备可能对应多个打开的文件。
5. STATUS iosDevAdd
(
DEV_HDR *pDevHdr,
char *name,
int drvnum
)
增加一个设备,该设备名称(路径)名为name,该设备的7个访问函数在对应的DRV_ENTRY结构数组中的下表为drvnum。如图2.11所示。
注意,在添加设备之前需要调用函数iosDevMatch检查已经添加的设备中是否包含相同的路径名(文件名)如果有则说明该设备已经加入了。返回错误。此外就是该函数只修改了iosDvList链,并不触及DRV_ENTRY结构数组的操作。
如果已经加入的设备中有“ab”这个名字,那么还可以增加名字为“abc”、“a”这样的设备,但是名字为“ab”的再不能添加了。
图2.11 在设备管理库中加入新设备
6. void iosDevDelete
(
DEV_HDR *pDevHdr /* pointer to device's structure */
)
从iosDvList链中删除一个设备。注意添加的时候DEV_HDR结构中的name是动态分配的,因此这里需要释放相应的空间。这里可以参考iosDevAdd函数,在iosDevAdd函数中参数pDevHdr也不是动态分配的。
7. DEV_HDR *iosDevFind
(
char *name, /* name of the device */
char **pNameTail /* where to put ptr to tail of name */
)
根据名字查找一个设备的DEV_HDR结构。查找遵照一下原则:
l 调用函数iosDevMatch在devTable数组中查找与name最为匹配的,也就是说查找的设备名字与name的前面n(n为设备名称的字符个数)个字符完全相同;
l 如果查到了匹配的,则返回值*pNameTail返回的则是name字符串中不包含设备名称的字符串;
l 如果没有找到匹配的设备,则返回默认的设备(如果有的话),此时*pNameTail返回的则是原name字符串。
8. LOCAL DEV_HDR *iosDevMatch
(
char *name
)
该函数的作用是查找已经添加的设备名中与参数name匹配度最大设备名。所谓匹配度最大就是说设备名正好是name字符串的前n(n为设备名称字符串长度)个字符完全相同中的所有设备中n最大的那一个设备。例如已经添加的设备中有“ab”、“abc”“bcde”,那么与name=“abcde”匹配最大的为“abc”。
9. DEV_HDR *iosNextDevGet
(
DEV_HDR *pDev
)
从node链中查找下一个node的DEV_HDR结构变量,参照前面的链表结构图。
10. int iosCreate
(
DEV_HDR *pDevHdr,
char *fileName,
int mode
)
该函数根据DEV_HDR结构指针pDevHdr中的drvNum数值作为下标,找到drvTable数组中的DEV_ENTRY结构变量中的de_create函数指针,调用de_create函数。
11. int iosDelete
(
DEV_HDR *pDevHdr,
char *fileName
)
类似于函数iosCreate()。
12. int iosOpen
(
DEV_HDR *pDevHdr,
char *fileName,
int flags,
int mode
)
类似于函数iosCreate()。
13. STATUS iosClose
(
int fd
)
类似于函数iosCreate()。
14. int iosRead
(
int fd,
char *buffer,
int maxbytes
)
类似于函数iosCreate()。
15. int iosWrite
(
int fd,
char *buffer,
int nbytes
)
类似于函数iosCreate()。
16. int iosIoctl
(
int fd,
int function,
int arg
)
基本的控制函数,主要完成几个功能。
function == FIOGETNAME,将文件描述符的名字复制到arg中;
其他情况则执行drvTable [pFdEntry->pDevHdr->drvNum].de_ioctl,这个类似于函数iosCreate()。
17. LOCAL void iosLock (void)
这个函数和iosUnlock共同管理一个二进制信号量,主要负责对fdTable、iosDevList的访问保护。主要进行互斥操作。