如果同时获得指向文件,则Linux上的打开文件句柄会发生什么:
为什么我要问这样的问题:我使用的是热插拔硬件(如USB设备等)。可能发生的是,设备(及其/ dev /文件)被用户或另一个Gremlin重新附加。
处理此问题的最佳做法是什么?
答案 0 :(得分:129)
如果文件被移动(在同一文件系统中)或重命名,则文件句柄保持打开状态,仍可用于读取和写入文件。
如果文件被删除,文件句柄仍然保持打开状态仍然可以使用(这不是某些人所期望的)。在最后一个句柄关闭之前,文件不会被真正删除。
如果文件被新文件替换,则具体取决于文件。如果文件的内容被覆盖,文件句柄仍然有效并访问新内容。如果现有文件未链接且使用相同名称创建新文件,或者如果使用rename()
将新文件移动到现有文件上,则它与删除相同(参见上文) - 即文件句柄将继续引用该文件的原始版本。
一般情况下,一旦文件打开,文件就会打开,没有人改变目录结构可以改变它 - 它们可以移动,重命名文件,或者在其位置放置其他内容,它只是保持打开状态。
在Unix中没有删除,只有unlink()
,这是有道理的,因为它不一定会删除文件 - 只是从目录中删除链接。
如果另一方面底层设备消失(例如USB拔出),则文件句柄将不再有效,并且可能在任何操作上给出IO /错误。你仍然需要关闭它。即使设备已重新插入,情况仍然如此,因为在这种情况下保持文件打开是不明智的。
答案 1 :(得分:7)
文件句柄指向不是路径的inode,因此大多数场景仍然可以正常工作,因为句柄仍然指向文件。
具体来说,对于删除场景 - 由于某种原因,该函数被称为“取消链接”,它会破坏文件名(dentry)和文件之间的“链接”。当您打开文件,然后取消链接时,文件实际上仍然存在,直到其引用计数变为零,即关闭句柄时。
编辑:对于硬件,您已打开特定设备节点的句柄,如果拔下设备,内核将无法访问所有设备,即使设备返回。您必须关闭设备并重新打开它。
答案 2 :(得分:3)
我不确定其他操作,但是对于删除:删除根本不会发生(物理上,即在文件系统中),直到关闭文件的最后一个打开句柄。因此,不应该从您的应用程序中删除文件。
一些应用程序(不会想到)依赖于这种行为,通过创建,打开和立即删除文件,这些文件的生存时间与应用程序一样长 - 允许其他应用程序了解第一个应用程序的生命周期无需查看流程图等。
类似的考虑也可能适用于其他东西。
答案 3 :(得分:3)
如果要检查文件处理程序(文件描述符)是否正常,可以调用此函数。
/**
* version : 1.1
* date : 2015-02-05
* func : check if the fileDescriptor is fine.
*/
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
/**
* On success, zero is returned. On error, -1 is returned, and errno is set
* appropriately.
*/
int check_fd_fine(int fd) {
struct stat _stat;
int ret = -1;
if(!fcntl(fd, F_GETFL)) {
if(!fstat(fd, &_stat)) {
if(_stat.st_nlink >= 1)
ret = 0;
else
printf("File was deleted!\n");
}
}
if(errno != 0)
perror("check_fd_fine");
return ret;
}
int main() {
int fd = -1;
fd = open("/dev/ttyUSB1", O_RDONLY);
if(fd < 0) {
perror("open file fail");
return -1;
}
// close or remove file(remove usb device)
// close(fd);
sleep(5);
if(!check_fd_fine(fd)) {
printf("fd okay!\n");
} else {
printf("fd bad!\n");
}
close(fd);
return 0;
}
答案 4 :(得分:2)
已删除文件的内存中信息(您提供的所有示例都是已删除文件的实例)以及磁盘上的inode仍然存在,直到文件关闭。
热插拔硬件是一个完全不同的问题,如果磁盘上的inode或元数据已完全改变,你不应该期望你的程序能够保持活着。
答案 5 :(得分:2)
以下实验表明MarkR's answer是正确的。
code.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <stdio.h>
void perror_and_exit() {
perror(NULL);
exit(1);
}
int main(int argc, char *argv[]) {
int fd;
if ((fd = open("data", O_RDONLY)) == -1) {
perror_and_exit();
}
char buf[5];
for (int i = 0; i < 5; i++) {
bzero(buf, 5);
if (read(fd, buf, 5) != 5) {
perror_and_exit();
}
printf("line: %s", buf);
sleep(20);
}
if (close(fd) != 0) {
perror_and_exit();
}
return 0;
}
数据:
1234
1234
1234
1234
1234
使用gcc code.c
生成a.out
。运行./a.out
。当您看到以下输出时:
line: 1234
使用rm data
删除data
。但是./a.out
将继续运行而不会出现错误,并产生以下整个输出:
line: 1234
line: 1234
line: 1234
line: 1234
line: 1234
我在Ubuntu 16.04.3上做过实验。
答案 6 :(得分:1)
在/ proc /目录下,您将找到当前处于活动状态的每个进程的列表,只需找到您的PID并且所有数据都在那里。有趣的信息是文件夹fd /,您将找到当前由该过程打开的所有文件处理程序。
最终你会找到一个指向你设备的符号链接(在/ dev /或甚至是/ proc / bus / usb /下),如果设备挂起链接将会死机,这个过程将无法刷新必须关闭并再次打开它(即使重新连接)
此代码可以读取PID的链接当前状态
#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
int main() {
// the directory we are going to open
DIR *d;
// max length of strings
int maxpathlength=256;
// the buffer for the full path
char path[maxpathlength];
// /proc/PID/fs contains the list of the open file descriptors among the respective filenames
sprintf(path,"/proc/%i/fd/",getpid() );
printf("List of %s:\n",path);
struct dirent *dir;
d = opendir(path);
if (d) {
//loop for each file inside d
while ((dir = readdir(d)) != NULL) {
//let's check if it is a symbolic link
if (dir->d_type == DT_LNK) {
const int maxlength = 256;
//string returned by readlink()
char hardfile[maxlength];
//string length returned by readlink()
int len;
//tempath will contain the current filename among the fullpath
char tempath[maxlength];
sprintf(tempath,"%s%s",path,dir->d_name);
if ((len=readlink(tempath,hardfile,maxlength-1))!=-1) {
hardfile[len]='\0';
printf("%s -> %s\n", dir->d_name,hardfile);
} else
printf("error when executing readlink() on %s\n",tempath);
}
}
closedir(d);
}
return 0;
}
这个最终代码很简单,你可以使用linkat函数。
int
open_dir(char * path)
{
int fd;
path = strdup(path);
*strrchr(path, '/') = '\0';
fd = open(path, O_RDONLY | O_DIRECTORY);
free(path);
return fd;
}
int
main(int argc, char * argv[])
{
int odir, ndir;
char * ofile, * nfile;
int status;
if (argc != 3)
return 1;
odir = open_dir(argv[1]);
ofile = strrchr(argv[1], '/') + 1;
ndir = open_dir(argv[2]);
nfile = strrchr(argv[2], '/') + 1;
status = linkat(odir, ofile, ndir, nfile, AT_SYMLINK_FOLLOW);
if (status) {
perror("linkat failed");
}
return 0;
}