Linux 文件与目录

时间:2024-01-17 21:48:26

文件描述符

在内核中,所有打开的文件都使用文件描述符(一个非负整数)标记。文件描述符的变化范围是0~OPEN_MAX – 1。早期的unix系统中,每个进程最多可以同时打开20个文件,就是说文件描述符的范围为0~19,但是现在很多系统将其增加到0~63。

#include <fcntl.h>

int open(const char* path, int oflag, ...);

int openat(int fd, const char* path, int oflag, ...);

返回值:成功,文件描述符;失败,-1

path: 打开或创建文件的名字。

oflag: 用|连接的多个选项。

以下选项有且只能有一个:

O_RDONLY(0)/O_WRONLY(1)/O_RDWR(2)/O_EXEC/O_SEARCH

以下选项可选:

O_APPEND/O_CLOEXEC/O_CREAT/O_DIRECTORY(如果path不是目录,则出错)/O_EXCL(如果同时指定了O_CREATE,而文件已经存在,则出错)/O_NOCTTY/O_NOFOLLOW(如果path是一个符号链接,则出错)/O_NBLOCK(非阻塞模式)/O_SYNC(使得每次write等待物理I/O完成)/O_TRUNC(将文件截断为0)/O_TTY_INIT(如果打开一个还未打开的终端设备,设置非标准的termios参数值,使其符合Single UNIX Specification)/O_DSYNC(使得每次write等待物理I/O完成)/O_RSYNC(使每一个以文件描述符为参数的read操作等待,直至对文件的所有写操作完成)

fd: 3种情况:

path为绝对路径,此时fd被忽略,openat等价于open;

path为相对路径,此时fd指定相对路径在文件系统中的开始地址,fd参数通过打开相对路径所在的目录来获取;

path为相对路径,fd的值为AT_FDCWD,此时路径名在当前目录中获取;

#include <unistd.h>

int close(int fd);

返回值:成功,0;失败,-1

当一个进程终止时,内核自动关闭所有打开的文件,因此,很多时候不需要显式地调用close。

#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

返回值:成功,新的文件偏移量;失败,-1

文件偏移量为从文件开始处计算出的字节数,非负。默认情况下,打开一个文件时,偏移量被设为0(除非以O_APPEND模式打开)。

whence:

如果为SEEK_SET(0),则将偏移量设为距文件开始处offset个字节;

如果为SEEK_CUR(1),则将偏移量设为距文件当前处offset个字节(可正可负);

如果为SEEK_END(2),则将偏移量设为文件长度加offset个字节(可正可负);

lseek仅将当前文件偏移量记录在内核中,用于下一次读或者写操作。文件偏移量可以大于当前文件长度,在这种情况下,对该文件的一次写操作将加长该文件,并在文件中形成一个空洞(属于文件但是没有写过的字节都被设为0),但是该空洞并不占用磁盘空间。

空洞程序示例:
[root@benxintuzi IO]# cat hole.c
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h> char buf1[] = "benxintuzi";
char buf2[] = "BENXINTUZI"; int main(void)
{
int fd; if ((fd = open("file", O_RDWR | O_CREAT)) == -)
printf("create error\n"); if (write(fd, buf1, ) != )
printf("buf1 write error\n");
/* offset now = 10 */ if (lseek(fd, , SEEK_SET) == -)
printf("lseek error\n");
/* offset now = 16384 */ if (write(fd, buf2, ) != )
printf("buf2 write error\n");
/* offset now = 16394 */ return ;
} [root@benxintuzi IO]# ls -l file
--wsr-S---. root root Aug : file
[root@benxintuzi IO]# od -c file
b e n x i n t u z i \ \ \ \ \ \
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
*
B E N X I N T U Z I

#include <unistd.h>

ssize_t read(int fd, void* buf, size_t nbytes);

返回值:成功,读出的字节数;失败,-1;遇到文件尾,0

#include <unistd.h>

ssize_t write(int fd, const void* buf, size_t nbytes);

返回值:成功,写入的字节数;失败,-1

#include <stdio.h>
#include <unistd.h> #define BUFFSIZE 4096 int main(void)
{
int n;
char buf[BUFFSIZE]; while((n = read(STDIN_FILENO, buf, BUFFSIZE)) > )
if(write(STDOUT_FILENO, buf, n) != n)
printf("write error\n"); if(n < )
printf("read error\n"); return ;
} [root@benxintuzi IO]# ./read
benxintuzi
benxintuzi
hello
hello
tuzi
tuzi
^C
[root@benxintuzi IO]#

文件共享

Unix支持在不同进程间共享打开的文件。Unix内核使用3种数据结构表示打开的文件:

Linux 文件与目录

注:创建v节点的目的是为了支持一个计算机系统上的多种文件系统类型。Linux系统没有使用v节点,而是使用了通用的i节点结构。

两个独立进程打开同一文件如下:

Linux 文件与目录

第一个进程在文件描述符3上打开该文件,第二个进程在文件描述符4上打开该文件。每个进程都获得独立的文件表项,但却指向同一个v节点。

#include <unistd.h>

int dup(int fd);

int dup2(int fd, int fd2);

函数返回新的文件描述符,与fd共享同一个文件表项。

说明:

由dup返回的文件描述符一定是当前可用的最小的文件描述符。对于dup2,可以用fd2指定特定的新的文件描述符。如果fd2已经打开,则先将其关闭;如果fd == fd2,则不关闭而直接返回。

 Linux 文件与目录

复制一个文件描述符的另一种方式是使用fcntl函数:

dup(fd)

等价于:

fcntl(fd, F_DUPFD, 0);

dup2(fd, fd2);

等价于:

close(fd2);

fcntl(fd, F_DUPFD, fd2);

#include <unistd.h>

int fsync(int fd);

int fdatasync(int fd);

void sync(void);

返回值:成功,0;失败,-1

说明:

sync:将修改过的缓冲区数据块放到写队列中,立即返回,而不等待实际写磁盘结束。

fsync:只对fd指定的文件块起作用,并且等待写磁盘结束才返回,同时更新文件的属性部分。

fdatasync:与fsync等价,只是其只关注数据部分,不更新文件属性部分。

#include <fcntl.h>

int fcntl(int fd, int cmd, ...);

说明:

fcntl函数的5种功能:

(1)   复制一个已有的文件描述符(cmd = F_DUPFD或F_DUPFD_CLOEXEC)

(2)   获取/设置文件描述符标志(cmd = F_GETFD或F_SETFD)

(3)   获取/设置文件状态标志(cmd = F_GETFL或F_SETFL)

(4)   获取/设置异步I/O所有权(cmd = F_GETOWN或F_SETOWN)

(5)   获取/设置记录锁(cmd = F_GETLK/F_SETLK/F_SETLKW)

/dev/fd目录下是名为0、1、2等的文件,打开文件/dev/fd/n等价于复制文件描述符n,因此,

fd = open(“/dev/fd/0”, mode);等价于fd = dup(0);

 

文件结构

#include <sys/stat.h>

int stat(const char* restrict pathname, struct stat* restrict buf);

int fstat(int fd, struct stat* buf);

int lstat(const char* restrict pathname, struct stat* restrict buf);

int fstatat(int fd, const char* restrict pathname, struct stat* restrict buf, int flag);

返回值:成功,0;失败,-1

stat函数系列返回相关的结构体信息。

fstat返回与fd相关的文件信息/lstat返回符号链接相关的信息/...

buf参数是一个结构体指针,由stat系列函数负责填充buf结构体。

struct stat

{

mode_t        st_mode;          /* file type & mode(permissions) */

ino_t         st_ino;           /* i-node number(serial number) */

dev_t         st_dev;           /* device number(file system) */

dev_t         st_rdev;          /* device number for special files */

nlink_t       st_nlink;         /* numbers of links */

uid_t         st_uid;           /* user ID of owner */

gid_t         st_gid;           /* group ID of owner */

off_t         st_size;          /* size in bytes, for regular files */

struct timespec st_atime;       /* time of last access */

struct timespec st_mtime;       /* time of last modification */

struct timespec st_ctime;       /* time of last file status change */

blksize_t     st_blksize;       /* best I/O block size */

blkcnt_t      st_blocks;        /* number of disk blocks allocated */

}

Unix文件类型

(1)   普通文件

(2)   目录文件:包含其他文件的名字以及指向这些文件的指针。

(3)   块特殊文件:提供对设备带缓冲的访问,每次访问以固定长度为单位进行。

(4)   字符特殊文件:每次访问长度单位可变(系统中的文件要么是字符特殊文件,要么是块特殊文件)。

(5)   FIFO:用于进程间通信。

(6)   套接字:用于进程间的网络通信,也可用于一台主机上的进程之间的非网络通信。

(7)   符号链接:指向另一文件。

可以用如下宏确定文件类型:

文件类型

S_ISREG()

普通文件

S_ISDIR()

目录文件

S_ISBLK()

块特殊文件

S_ISCHR()

字符特殊文件

S_ISFIFO()

FIFO

S_ISSOCK()

套接字

S_ISLNK()

符号链接

[root@benxintuzi IO]# cat types.c
#include <sys/stat.h>
#include <stdio.h> int main(int argc, char** argv)
{
int i;
struct stat buf;
char* ptr; for(i = ; i < argc; i++)
{
printf("%s: ", argv[i]);
if(lstat(argv[i], &buf) < )
{
printf("lstat error\n");
continue;
}
if(S_ISREG(buf.st_mode))
ptr = "regular";
else if(S_ISDIR(buf.st_mode))
ptr = "directory";
else if(S_ISBLK(buf.st_mode))
ptr = "block special";
else if(S_ISCHR(buf.st_mode))
ptr = "character special";
else if(S_ISFIFO(buf.st_mode))
ptr = "fifo";
else if(S_ISLNK(buf.st_mode))
ptr = "symbolic link";
else if(S_ISSOCK(buf.st_mode))
ptr = "socket";
else
ptr = "unknown mode";
printf("%s\n", ptr);
} return ;
} [root@benxintuzi IO]# gcc types.c -o types
[root@benxintuzi IO]# ./types /etc/passwd /etc /dev/tty /dev/cdrom
/etc/passwd: regular
/etc: directory
/dev/tty: character special
/dev/cdrom: symbolic link

文件访问权限

用户ID和组ID

实际用户ID

实际组ID

登录时取自口令文件中的登录项,表明是谁在登录系统

有效用户ID

有效组ID

附属组ID

表明了对文件的访问权限

设置用户ID

设置组ID

保存了有效用户ID和有效组ID的副本

通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID

所有类型的文件都有访问权限设定,可以分为三类,共有9个权限位:

st_mode屏蔽

说明

S_IRUSR

S_IWUSR

S_IXUSR

用户读

用户写

用户执行

S_IRGRP

S_IWGRP

S_IXGRP

组读

组写

组执行

S_IROTH

S_IWOTH

S_IXOTH

其他读

其他写

其他执行

进程每次打开、创建或者删除一个文件时,内核会进行文件访问权限测试,具体如下(顺序执行):

(1)   若进程的有效用户ID是0(超级用户),则允许访问;

(2)   若进程的有效用户ID等于文件的所有者ID,则根据所有者的权限置位情况开放给用户相应权限;

(3)   若进程的有效组ID等于文件的组ID,则根据组的权限置位情况开放相应权限;

(4)   若其他用户的适当权限位被设置,则开放相应权限。

#include <unistd.h>

int access(const char* pathname, int mode);

int faccessat(int fd, const char* pathname, int mode, int flag);

返回值:成功,0;失败,-1

说明:

access函数对按实际用户ID和实际组ID进行访问权限测试。

关于mode,如果测试文件存在,则mode设为F_OK;否则设为R_OK/W_OK/X_OK的按位或。

关于flag,如果flag设置为AT_EACCESS,则按有效用户ID和有效组ID进行测试。

[root@benxintuzi IO]# cat access.c
#include <fcntl.h>
#include <stdio.h> int main(int argc, char** argv)
{
if(argc != )
{
printf("usage: execfile <pathname>\n");
return ;
}
if(access(argv[], R_OK) < )
printf("access error for %s\n", argv[]);
else
printf("read access OK\n"); return ;
} [root@benxintuzi IO]# gcc access.c -o access
[root@benxintuzi IO]# ./access
usage: execfile <pathname>
[root@benxintuzi IO]# ./access types.c
read access OK
[root@benxintuzi IO]# ./access types
read access OK
[root@benxintuzi IO]# ./access /
read access OK

#include <sys/stat.h>

mode_t umask(mode_t cmask);

说明:

在进程创建一个新文件或新目录时,必须使用文件模式屏蔽字,其指定了新文件的访问权限位。屏蔽字中为1表示模式中的相应位被关闭。Unix系统的大多数用户从不处理umask值。尽管如此,当创建新文件时,如果我们想确保指定的访问权限已经激活,那么必须在进程运行时修改umask值。

#include <fcntl.h>
#include <stdio.h> #define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) int main(void)
{
umask(); # 开放全部权限
if(creat("foo", RWRWRW) < )
printf("create error for foo\n"); umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); # 关闭组和其他用户的访问权限
if(creat("bar", RWRWRW) < )
printf("create error for bar\n"); return ;
} [root@benxintuzi IO]# ./umask
[root@benxintuzi IO]# ls -l foo bar
-rw-------. root root Aug : bar
-rw-rw-rw-. root root Aug : foo

#include <sys/stat.h>

int chmod(const char* pathname, mode_t mode);

int fchmod(int fd, mode_t mode);

int fchmodat(int fd, const char* pathname, mode_t mode, int flag);

返回值:成功,0;失败,-1

说明:

函数用于改变现有文件的访问权限。

chmod对指定文件进行操作,fchmod/fchmodat对已打开的文件进行操作。

mode是如下常量的按位或:

mode

说明

S_ISUID

S_ISGID

S_ISVTX

执行时设置用户ID

执行时设置组ID

保存正文(粘着位)

注:早期的Unix称为粘着位,现在称为保存正文位。设置该模式后,文件的正文部分总是保存在交换区中,以便下次在执行该程序时可以较快地载入内存。目前的系统扩展了粘着位的使用范围,可以对目录设置粘着位。需要说明的是,如果对一个目录设置了粘着位,只有对该目录拥有写权限并且满足如下条件之一才能重命名或删除该目录下的文件:(拥有此文件、拥有此目录、是超级用户),如/tmp、/var/tmp目录都被设置了粘着位,任何用户都可以在这两个目录中创建文件,但是不能删除或重命名属于他人的文件。

S_IRWXU

S_IRUSR

S_IWUSR

S_IXUSR

用户读、写、执行

用户读

用户写

用户执行

S_IRWXG

S_IRGRP

S_IWGRP

S_IXGRP

组读、写、执行

组读

组写

组执行

S_IRWXO

S_IROTH

S_IWOTH

S_IXOTH

其他读、写、执行

其他读

其他写

其他执行

[root@benxintuzi IO]# cat chmod.c
#include <sys/stat.h>
#include <stdio.h> int main(void)
{
struct stat statbuf; /* turn on set-group-ID and turn off group-execute */
if(stat("foo", &statbuf) < )
printf("stat error for foo\n");
if(chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < )
printf("chmod error for foo"); /* set absolute mode to "rw-r--r--" */
if(chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < )
printf("chmod error for bar"); return ;
} [root@benxintuzi IO]# gcc chmod.c -o chmod
[root@benxintuzi IO]# ./chmod
[root@benxintuzi IO]# ls -l foo bar
-rw-r--r--. root root Aug : bar
-rw-rwSrw-. root root Aug : foo

#include <unistd.h>

int chown(const char* pathname, uid_t owner, gid_t group);

int fchown(int fd, uid_t owner, gid_t group);

int fchownat(int fd, const char* pathname, uid_t owner, gid_t group, int flag);

int lchown(const char* pathname, uid_t owner, gid_t group);

返回值:成功,0;失败,-1

chown函数用于改变文件的用户ID和组ID。

#include <unistd.h>

int truncate(const char* pathname, off_t length);

int ftruncate(int fd, off_t length);

返回值:成功,0;失败,-1

将文件截断为length个字节。如果文件当前大于length,则截断;如果小于length,则增加。增加的部分数据为0(就是一个空洞)。

文件系统

Linux 文件与目录

Linux 文件与目录

Linux 文件与目录

#include <unistd.h>

int link(const char* existingpath, const char* newpath);

int linkat(int efd, const char* existingpath, int nfd, const char* newpath, int flag);

返回值:成功,0;失败,-1

说明:

创建一个指向现有文件的链接。

引用现有文件existingpath创建新的newpath,实则是增加一个链接计数。

#include <unistd.h>

int unlink(const char* pathname);

int unlinkat(int fd, const char* pathname, int flag);

返回值:成功,0;失败,-1

说明:

删除目录项,并将pathname所引用的文件的链接数减1。如果链接计数减少为0,则删除该文件。

#include <stdio.h>

int rename(const char* oldname, const char* newname);

int renameat(int oldfd, const char* oldname, int newfd, const char* newname);

返回值:成功,0;失败,-1

符号链接

硬链接是直接指向文件的i节点,通常要求链接和文件位于同一文件系统中。只有超级用户才能创建指向目录的硬链接。

相对地,符号链接是对一个文件的间接指针,符号链接指向的对象没有特殊限制,任何用户都可以创建指向目录的符号链接。其一般用户将一个文件或目录结构移到系统中的另一个位置。

#include <unistd.h>

int symlink(const char* actualpath, const char* sympath);

int symlinkat(const char* actualpath, int fd, const char* sympath);

返回值:成功,0;失败,-1

说明:

创建符号链接。actualpath和sympath可以不位于同一文件系统中。

#include <unistd.h>

ssize_t readlink(const char* restrict pathname, char* restrict buf, size_t bufsize);

ssize_t readlinkat(int fd, const char* restrict pathname, char* restrict buf, size_t bufsize);

返回值:成功,读到的字节数;失败,-1

说明:

由于open函数打开符号链接时,是打开链接指向的实际文件,因此需要readlink打开符号链接本身,将链接本身信息存入buf中。

关于时间

每个文件需要维护3个时间字段:

字段

说明

st_atim

文件的最后访问时间

st_mtim

文件的最后修改时间

st_ctim

i节点的最后更改时间

#include <sys/stat.h>

int futimens(int fd, const struct timespec times[2]);

int utimensat(int fd, const char* path, const struct timespec times[2], int flag);

返回值:成功,0;失败,-1

说明:

函数用于更改一个文件的访问和修改时间,可以精确到纳秒级。

times数组的第一个元素是访问时间,第二个是修改时间,有4种指定方式:

(1)   如果times参数是一个空指针,则访问时间和修改时间都设置为当前时间;

(2)   如果times参数指向两个timespec结构,并且若timespec结构的tv_nsec字段为UTIME_NOW,则相应时间设为当前时间,忽略对应的tv_sec;

(3)   如果times参数指向两个timespec结构,并且若timespec结构的tv_nsec字段为UTIME_OMIT,则相应时间保持不变,忽略对应的tv_sec;

(4)   如果times参数指向两个timespec结构,并且若timespec结构的tv_nsec字段既非UTIME_NOW,也非UTIME_OMIT,则相应时间设为tv_sec和tv_nsec。

关于目录

#include <sys/stat.h>

int mkdir(const char* pathname, mode_t mode);

int mkdirat(int fd, const char* pathname, mode_t mode);

#include <unistd.h>

int rmdir(const char* pathname);

#include <dirent.h>

DIR* opendir(const char* pathname);

DIR* fdopendir(int fd);

struct dirent* readdir(DIR* dp);

void rewinddir(DIR* dp);

int closedir(DIR* dp);

long telldir(DIR* dp);

void seekdir(DIR* dp, long loc);

#include <unistd.h>

int chdir(const char* pathname);

int fchdir(int fd);

说明:

更改当前进程的工作目录,不影响其他进程。

#include <unistd.h>

char* getcwd(char* buf, size_t size);

返回当前工作目录的绝对路径,存入buf中。

设备特殊文件

每个文件系统所在的存储设备都由其主、次设备号表示,数据类型为dev_t。主设备号标识驱动程序,此设备号标识特定的子设备。因此,在同一磁盘驱动器上的各个文件系统通常具有相同的主设备号,但是次设备号却不同。宏major和minor用来访问主、次设备号,Linux将其定义在<sys/sysmacros.h>中,而该头文件又包含在<sys/types.h>中。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h> int main(int argc, char** argv)
{
int i;
struct stat buf;
for(i = ; i < argc; i++)
{
printf("%s: ", argv[i]);
if(stat(argv[i], &buf) < )
{
printf("stat error\n");
continue;
}
printf("dev = %d/%d ", major(buf.st_dev), minor(buf.st_dev));
if(S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode))
{
printf("rdev = %d/%d", major(buf.st_rdev), minor(buf.st_rdev));
}
printf("\n");
} return ;
} [root@benxintuzi IO]# ./dev / /home /dev/tty[]
/: dev = /
/home: dev = /
/dev/tty0: dev = / rdev = /
/dev/tty1: dev = / rdev = /