背景
嵌入式系统中,一般均可通过写寄存器的方式进行整个系统的复位。该种复位方式类似于直接下电。若在复位时,存在对文件系统的读写,则可能造成文件系统的损坏。因此,在复位前,需要针对文件系统进行保护。
老复位流程
老复位流程如下:
(1) 调整当前任务优先级为99;
(2) 给所有进程(除自己)发SIGTERM以及SIGKILL信号;
(3) 关闭所有fd;
(4) umount所有文件系统;
(5) 写寄存器复位
若直接使用该流程,在复位过程中,会看到大量与错误打印,大多数和套接字、消息队列等有关系。查看系统错误码(0x9)含义为bad file description。对比上述流程,在步骤(3)中就会将所有fd关闭。对于Linux操作系统,“一切皆文件”,因此系统中套接字、消息队列等等均需使用fd。在telnet控制台上输入ls –al /proc/进程pid/fd,即可查看进程已打开的所有fd,打印如下(仅列出部分):
lrwx------ 1 root root 64 Mar 2 05:54 1 -> socket:[232]
l-wx------ 1 root root 64 Mar 2 05:54 10 -> pipe:[23561]
lrwx------ 1 root root 64 Mar 2 05:54 101 -> /MsgQ202
lrwx------ 1 root root 64 Mar 2 05:54 102 -> socket:[24765]
lrwx------ 1 root root 64 Mar 2 05:54 103 -> socket:[24766]
lrwx------ 1 root root 64 Mar 2 05:54 104 -> socket:[24767]
lrwx------ 1 root root 64 Mar 2 05:54 105 -> /MsgQ2
问题分析
出现刷屏打印,是由于错误地关闭了socket、消息队列等的fd,接收任务使用这些fd时,fd已经非法,导致刷屏打印。另一方面,关闭fd的目的实际上是为了后面umount文件系统,所以,我们不需要关闭所有已打开的fd,只需要关闭与文件系统有关的文件即可。
问题解决
通过fstat甄别
与fd有关的首先想到的是fstat函数,原型如下:
int fstat(int filedes, struct stat *buf);
通过传递fd,可以获取一个struct stat结构,struct stat结构如下:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
通过下列宏,可以判断出当前文件的文件类型,参数为stat结构中的st_mode:
S_ISREG() 普通文件
S_ISRDIR() 目录文件
S_ISCHR() 字符特殊文件
S_ISBLK() 块特殊文件
S_ISFIFO() 管道或FIFO
S_ISLNK() 符号链接
S_ISSOCK() 套接字
在程序中首先针对fd调用fstat,然后使用S_ISREG进行判断,当为普通文件时,才执行close操作。使用该方案后,仍然会打印消息队列相关的失败。而之前套接字相关的打印已经消失,说明,我们已经剥离出了fd是socket的情况,但是仍然没有剥离出是消息队列的情况。
通过阅读APUE,POSIX.1允许将进程间通信(IPC)对象(如消息队列和信号量等)说明为文件。可以使用下面的宏来进行判断,参数是stat结构指针:
S_TYPEISMQ() 消息队列
S_TYPEISSEM() 信号量
S_TYPEISSHM() 共享存储对象
在代码里再次加入这些判断,即必须为普通文件(S_ISREG)且不能为消息队列、信号量、以及共享存储对象中的任意一个,加入后仍然刷打印。注意到,APUE中提到的进程间通信,而我们刷打印的应该是一个线程级别的消息队列。
转化为绝对路径甄别
另外一种思路,就是将fd转化为绝对路径,通过判断绝对路径中,是否含有存储设备的挂载点(/ata1,/ata2,/flashDev,/bin/tool),仅关闭含有挂载点的文件。这里使用的函数是readlink函数,该函数获取一个符号链接所指向的路径。在/proc/进程号/fd目录下的,实际上均是符号链接,我们只需要遍历fd下的所有文件,获取其链接指向的路径,然后判断其中是否有挂载点即可。
为了验证该方法的准确性,我们首先使用creat函数,在/ata1下创建一个目录:
creat "/ata1/test",0677
value = 264 = 0x108
返回值264即为打开的文件指针,然后去/proc/进程ID/fd目录下寻找该fd,如下:
l-wx------ 1 root root 64 Mar 2 12:25 264 -> /ata1/test
通过在复位前的LOG,我们确实针对该fd进行了关闭。