APUE笔记---第四章Linux文件系统与文件目录操作

APUE笔记—第四章Linux文件系统与文件目录操作

1. linux文件系统

1.1 ext2文件系统

我们可以把一个磁盘分成一个或多个分区,每个分区可以包含一个文件系统。下图是一个磁盘分区格式化成ext2文件系统后的存储布局。
APUE笔记---第四章Linux文件系统与文件目录操作_第1张图片
- 文件系统中存储的最小单位是块(Block),一个块究竟多大是在格式化时确定的,例如mke2fs的-b选项可以设定块大小为1024、2048或4096字节。而上图中启动块(BootBlock)的大小是确定的,就是1KB,用来存储磁盘分区信息和启动信息,任何文件系统都不能使用启动块。
- 启动块之后才是ext2文件系统的开始,ext2文件系统将整个分区划成若干个同样大小的块组(Block Group),每个块组都由以下部分组成。

点击查看关于启动块的介绍和管理的文章

  • 超级块(Super Block) 描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次mount的时间等等。超级块在每个块组的开头都有一份拷贝。
  • 块组描述符表(GDT,Group Descriptor Table) 由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符(Group Descriptor)存储一个块组的描述信息,例如在这个块组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。和超级块类似,块组描述符表在每个块组的开头也都有一份拷贝,这些信息是非常重要的,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此它们都有多份拷贝。通常内核只用到第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。
  • 块位图(Block Bitmap) 一个块组中的块是这样利用的:数据块存储所有文件的数据,比如某个分区的块大小是1024字节,某个文件是2049字节,那么就需要三个数据块来存,即使第三个块只存了一个字节也需要占用一个整块;超级块、块组描述符表、块位图、inode位图、inode表这几部分存储该块组的描述信息。那么如何知道哪些块已经用来存储文件数据或其它描述信息,哪些块仍然空闲可用呢?块位图就是用来描述整个块组中哪些块已用哪些块空闲的,它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。为什么用df命令统计整个磁盘的已用空间非常快呢?因为只需要查看每个块组的块位图即可,而不需要搜遍整个分区。相反,用du命令查看一个较大目录的已用空间就非常慢,因为不可避免地要搜遍整个目录的所有文件。
  • inode位图(inode Bitmap) 和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。
  • inode表(inode Table) 我们知道,一个文件除了数据需要存储之外,一些描述信息
    也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一个块组中的所有inode组成了inode表。
  • 数据块(Data Block) 根据不同的文件类型有以下几种情况

    1. 对于常规文件,文件的数据存储在数据块中。
    2. 对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。
    3. 对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
      4.* 设备文件、FIFO和socket等特殊文件*没有数据块,设备文件的主设备号和次设备号保存在inode中。

1.2数据块寻址

inode中存着许多inode,每个inode占128B,每个inode存放着文件属性和数据块指针,数据块指针呢占60B。下图表示如何根据inode找到数据块。
APUE笔记---第四章Linux文件系统与文件目录操作_第2张图片
inode进行寻址有两种方式:
- 直接寻址,也就是inode记录了数据存放的数据块的编号。
- 间接寻址,间接寻址又分成一级间接寻址,二级间接寻址,三级间接寻址。以一集间接寻址为例,inode记录的是一个数据块编号,这个数据块不存放数据,而是分成若干个(数据块大小/4B个)记录,每个记录存放数据块的编号,这些数据块存放数据。通过这么一级一级的扩大数据存放范围。

举一个例子,假如一个数据块Block大小为4KB,每个数据块编号为4B,那么每个Block就可以寻址4KB/4B = 1K个数据块。

  • 12个直接寻址——> 12*4KB = 48KB
  • 一级间接寻址——> 1K * 4KB = 4MB
  • 二级间接寻址——> 1K * 1K * 4KB = 4GB
  • 三级间接寻址——> 1K * 1K * 1K * 4KB = 4TB

这样,一个inode就可以寻址一个48KB+4MB+4GB+4TB大小的文件,已经足够了。

2. 函数stat、fstat、lstat

 #include 
 #include 
 #include 

 int stat(const char *pathname, struct stat *buf);
 int fstat(int fd, struct stat *buf);
 int lstat(const char *pathname, struct stat *buf);
//返回值:若成功,返回0;若出错,返回-1
  1. stat函数返回与pathname有关的信息结构。stat称为跟踪链接。
  2. fstat函数获得已在描述符fd上打开的文件的有关信息。
  3. lstat函数与stat函数类似,但当命令文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用的文件的信息。lstat称为不跟踪符号链接。

这三个函数有一个相同类型的参数struct stat *类型的buf,buf是一个指针,指向这么一个结构

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 512B blocks allocated */

    struct timespec st_atim;  /* time of last access */
    struct timespec st_mtim;  /* time of last modification */
    struct timespec st_ctim;  /* time of last status change */

#define     st_atime    st_atim.tv_sec      /* Backward compatibility */
#define     st_mtime    st_mtim.tv_sec
#define     st_ctime    st_ctim.tv_sec
};

3. 文件类型

  1. 普通文件(Regular file)。无论该文件是二进制文件还是文本文件,对于内核来说并无区别。
  2. 目录文件(Directory)。这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对于一个目录文件,只有具有读权限的进程可以读该目录的内容,但只有内核可以写目录文件。
  3. 块特殊文件(Block special file)。这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
  4. 字符特殊文件(Character special file)。这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变。系统总的所有设备要么是字符特殊文件,要么是块特殊文件。
  5. FIFO(First Input First Output)。先入先出队列,用于进程间通信,有时也称为命名管道(named pipe)
  6. 套接字(socket)。这种类型的文件用于进程间的网络通信。套接字也用于在一台宿主机上进程之间的非网络通信。
  7. 符号链接(Symbolic link)。这种类型文件指向另一个文件。

这些文件类型信息包含在stat结构的st_mode成员中,

文件类型
S_ISLNK(m) 符号链接
S_ISREG(m) 普通文件
S_ISDIR(m) 目录文件
S_ISCHR(m) 字符特殊文件
S_ISBLK(m) 块特殊文件
S_ISFIFO(m) 管道或FIFO
S_ISSOCK(m) 套接字

例如S_ISLNK(m)的宏定义为:

#define S_ISLNK(m)      (((m) & S_IFMT) == S_IFLNK)

4. 设置用户ID和设置组ID

  • 实际用户ID实际组ID标识我们究竟是谁
  • 有效用户ID有效组ID以及附属组ID决定了我们的文件访问权限
  • 保存的设置用户ID保存的设置组ID在执行时一个程序时包含了有效用户ID和有效组ID的副本

每一个文件都有一个所有者所有组,所有者由stat结构中的st_uid指定,所有组则由st_gid指定。
通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。
当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID就是实际组ID。但是,文件的模式字中设置一个特殊标志,例如passwd命令,它执行的文件是/usr/bin/passwd这个文件,这个文件设置了特殊标志

menwen@menwen:~/$ ls -l /usr/bin/passwd 
-rwsr-xr-x 1 root root 53128 329  2016 /usr/bin/passwd

这个s权限的意思是:当执行此文件时,将进程的有效用户ID设置成为文件所有者的用户ID。相当于以root用户来执行该文件。在文件模式字中的这一位就是设置用户ID位,设置组ID位就是所有组上的”s”标志位。

5. 文件访问权限

每个文件有9个访问权限位,可以分成三类。

st_mode屏蔽 含义
S_IRUSR 用户读
S_IWUSR 用户写
S_IXUSR 用户执行
S_IRGRP 组读
S_IWGRP 组写
S_IXGRP 组执行
S_IROTH 其他读
S_IWOTH 其他写
S_IXOTH 其他执行

chamod(1)命令用于修改这9个权限位。u表示所有者,g表示组,o表示其他。

6. 函数access

#include 

int access(const char *pathname, int mode);
//返回值:若成功,返回0,若出错,返回-1

mode的说明

mode 说明
R _OK 测试读权限
W_OK 测试写权限
X_OK 测试执行权限
F_OK 测试文件是否存在

内核测试的步骤:(按照下面的顺序)
1. 若进程的有效用户ID是0(超级用户),则允许访问。
2. 若进程的有效用户ID等于文件按所有者ID,那么如果所有者适当的访问权限被设置,则允许访问。意思是:如果进程要进行写操作,那么就检查用户写位是否为1。
3. 若进程的有效组ID或附属组ID之一等于文件的组ID,那么如果组适当的访问权限位被设置,则允许访问。
4. 若其他用户适当的访问权限位被设置,则允许访问。
注意:如果进程拥有此文件,则按照用户访问权限批准或拒绝该进程对文件的访问——不查看组访问权限,其他类似。

access函数的实例

#include 
#include 
#include 
#include 
#include 

int main(int argc, char* argv[])
{
        if(argc < 2){
                printf("./aut filename\n");
                exit(1);
        }

        if(access(argv[1], F_OK) < 0){
                perror("access");
                exit(1);
        }else{
            printf("%s : file exist\n", argv[1]);
    }

    if(access(argv[1], R_OK) < 0){
            perror("access error");
    }else{
        printf("read access OK\n");
    }

    if(open(argv[1], O_RDONLY) < 0){
        perror("open error");
        exit(1);
    }else{
        printf("open read OK\n");
    }

        return 0;
}

下面是程序的实例会话:

menwen@menwen:~/$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1344 1010 20:28 /etc/shadow
menwen@menwen:~/$ ls -l a.out 
-rwsrwxr-x 1 root menwen 7532 1027 19:52 a.out
menwen@menwen:~/$ ./a.out /etc/shadow
/etc/shadow : file exist
access error: Permission denied
open error: Permission denied

当将a.out程序文件的所有者改为root,并打开设置用户的ID(加s标志)

sudo chown root a.out
sudo chmod u+s a.out

menwen@menwen:~/APUE_code/FILE_DIR$ ./a.out /etc/shadow
/etc/shadow : file exist
access error: Permission denied
open read OK

虽然可以open函数打开,但是设置用户ID程序可以确定实际用户不能正常读指定的文件。

7. 函数umask

函数umask可以为进程设置文件模式创建屏蔽字,并返回之前的值。

#include 
#include 

mode_t umask(mode_t mask);
//返回值:之前的文件模式创建的屏蔽字

cmask参数就是标题5—-文件访问权限的9个常量中的若干个按位“或”构成的。

umask程序实例

#include 
#include 
#include 

#define RWRWRW  (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

int main()
{
        umask(0);
        if(open("foo", O_CREAT | O_RDONLY, RWRWRW) < 0){
                perror("open foo err");
        }
        umask(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
        if(open("bar", O_RDONLY | O_CREAT, RWRWRW) < 0){
                perror("open bar err");
        }

        return 0;
}

运行结果如下:

menwen@menwen:~/$ ls -l bar foo
-------rw- 1 menwen menwen 0 1027 20:22 bar
-rw-rw-rw- 1 menwen menwen 0 1027 20:22 foo

8.函数chmod和fchmod

这俩个函数可以是我们更改现有的文件的访问权限

#include 

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
//返回值:若成功,返回0,若出错,返回-1
  • chmod函数在指定文件上进行操作。
  • fchmod函数对已经打开的文件进行操作。
  • 如果要改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。

mode参数除了之前的9个,还有6个如下

mode 说明
S_ISUID 执行时设置用户ID
S_ISGID 执行时设置组ID
S_ISVTX 保存正文(粘着位)
S_IRWXU 所有者读、写、执行
S_IRWXG 组读、写、执行
S_IRWXO 其他读、写、执行

chmod函数的实例:

#include 
#include 
#include 

int main()
{
        struct stat buf;

        if(stat("foo", &buf) < 0){
                perror("stat err");
                exit(1);
        }
        if(chmod("foo", (buf.st_mode & ~S_IXGRP) | S_ISGID) < 0){
                perror("chmod foo err");
                exit(1);
        }

        return 0;
}

运行结果:

menwen@menwen:~/$ ls -l foo 
-rw-rwSrw- 1 menwen menwen 0 1027 20:22 foo

注意:

  • 不管文件的权限位如何,都会被设置为一个绝对值
  • chmod函数只跟新i节点最近一次被更改的时间。更改的是buf中的st_ctim值

关于粘着位

  • 由来:当程序第一次被执行,在其终止的时候,程序正文部分的一个副本仍被保存在交换区,使得下次执行程序时较快的将其装载进内存,因为在系统每次自举前,文件的正文部分总在交换区中,因此就有了名字“粘着”的由来。
  • UNIX也称保存正文位(saved-text bit)因此有了常量S_ISVTX。
  • 粘着位只针对目录文件,例如:/tmp和/var/tmp
drwxrwxrwt  16 root root  4096 10月 27 21:01 tmp
  • 含义:任何用户都可以在这两个目录中创建文件,读写执行文件,但是不能删除或者重命名其他人文件。

9. 函数chown、fchown、lchown

可以更改文件的用户ID和组ID,如果连个owner或者group中的任意一个是-1,则对应的ID不变。

#include 

int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);
//返回值:若成功,返回0;若出错,返回0
  • 在符号链接情况下,lchmod只改变符号链接本身的所有者,而不是该符号链接所指向的文件的所有者。
  • fchmod函数改变fd参数指向的打开文件的所有者,既然它在一个已经打开的文件上操作,那么就不能用于改变符号链接的所有者。
  • 这些函数使用时必须具有root权限。

chmod函数的实例:

#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
        struct stat buf;
        if(stat(argv[1], &buf) < 0){
                perror("stat err");
                exit(1);
        }
        printf("uid = %d\tgid = %d\n", buf.st_uid, buf.st_gid);

        if(chown(argv[1], 0, 0) < 0){//所有者和所有组改为root
                perror("chown err");
                exit(1);
        }
        if(stat(argv[1], &buf) < 0){
                perror("stat err");
        }
        printf("uid = %d\tgid = %d\n", buf.st_uid, buf.st_gid);

        return 0;
}

将程序改所有者为root,且打开设置用户ID位

sudo chown root a.out
sudo chmod u+s a.out
menwen@menwen:~/$ ll a.out 
-rwsrwxr-x 1 root menwen 7584 1027 22:30 a.out*

menwen@menwen:~/$ ll file
-rw-rw-r-- 1 menwen menwen 0 1027 22:36 file
menwen@menwen:~/$ ./a.out file
uid = 1000  gid = 1000
uid = 0     gid = 0
menwen@menwen:~/$ ll file
-rw-rw-r-- 1 root root 0 1027 22:36 file

10. 文件长度

stat结构成员st_size表示以字节位单位的文件长度。此字段只对普通文件目录文件符号链接有意义。
linux系统提供了st_blksize和st_blocks两个字段。

  • st_blksize是对文件I/O较合适的块长度
  • st_blocks是所分配的实际521字节块块数。

10.1 文件中的空洞

空洞是由所设置的偏移量超过文件尾端,并写入一些数据后造成的。
实例:

#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
        int n;
        struct stat buf;//文件信息的缓冲区
        int fd = open(argv[1], O_WRONLY | O_CREAT, 0664);//创建一个文件

        if(fstat(fd, &buf) < 0){
                perror("fstat err");
                exit(1);
        }
        printf("blksize = %ld\tblocks = %ld\n", buf.st_blksize, buf.st_blocks);
    //打印出st_blksize和st_blocks
        if(fd < 0){
                perror("open err");
                exit(1);
        }
        lseek(fd, 0x1000, SEEK_SET);//偏移4096个字节
        if(write(fd, "test", 4) < 0){//写一个"test"
                perror("write err");
                exit(1);
        }
        close(fd);

        fd = open(argv[1], O_WRONLY);
        if(fstat(fd, &buf) < 0){
                perror("fstat err");
                exit(1);
        }
        printf("blksize = %ld\tblocks = %ld\n", buf.st_blksize, buf.st_blocks);
    //打印写成文件的st_blksize和st_blocks
        close(fd);
        return 0;
}

运行结果如下:

menwen@menwen:~/$ ./a.out file.core
blksize = 4096  blocks = 0
blksize = 4096  blocks = 8
menwen@menwen:~/$ ls -l file.core 
-rw-rw-r-- 1 menwen menwen 4100 1028 15:58 file.core
menwen@menwen:~/$ du -s file.core 
4   file.core
menwen@menwen:~/$ wc -c file.core 
4100 file.core

根据打印出的blksize和blocks可以得知;

  • 该系统的文件IO最适合的块长度是4096
  • file.core文件实际分配512字节块快数为8块

根据du -s、ls -l、wc -c这三个命令可以看出:
文件长度位4100字节,但du命令报告该文件所使用的磁盘空间总量为4个512字节块,所以文件中有空洞。

如果复制一个空洞文件,那么空洞文件会被填满,其中的所有实际数据字节皆填写为0.

menwen@menwen:~/$ cat file.core > file.copy
menwen@menwen:~/$ ls -l file*
-rw-rw-r-- 1 menwen menwen 4100 10月 28 16:20 file.copy
-rw-rw-r-- 1 menwen menwen 4100 10月 28 16:19 file.core
menwen@menwen:~/$ du -s file*
8 file.copy
4 file.core

11.文件截断

打开文件时使用O_TRUNC标志可以将文件截断位0
函数truncate和ftruncate可以截断文件

#include 
#include 

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
//返回值:若成功,返回0,若出错,返回-1
  • 这两个函数将一个文件的长度截断位length;
  • 如果文件的长度大于length,则超过length的数据不能访问;
  • 如果文件的长度小于length,则文件长度增加,以前文件尾端和新的文件尾端之间的数据读为0。

函数实例:

#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
        if(truncate(argv[1], 500) < 0){//把文件截断到500字节
                perror("truncate err");
                exit(1);
        }

        return 0;
}

运行结果:

menwen@menwen:~/$ ll file 
-rw-rw-r-- 1 menwen menwen 1000 1028 16:37 file
menwen@menwen:~/$ ./a.out file 
menwen@menwen:~/$ ll file 
-rw-rw-r-- 1 menwen menwen 500 1028 16:39 file

12. 函数link、unlink、remove

使用link函数创建一个指向现有文件的链接

#include 

int link(const char *oldpath, const char *newpath);
//返回值:若成功,返回0, 若出错,返回-1
  • 这个函数创建一个新目录项newpath,它引用现有的文件,如果newpath已经存在,则返回出错。只创建newpath最后一个分量,路径中的其他部分应当已经存在。
  • 创建新目录项和增加链接计数应当是一个原子操作
  • 不允许对目录创建硬链接,因为有可能在文件系统中形成循环。

12.2 函数实例

#include 
#include 
#include 
#include 

int main()
{
        if(link("file", "file.link") < 0){
                perror("link err");
                exit(1);
        }
}

运行结果:

menwen@menwen:~/$ df .
文件系统          1K-块    已用     可用 已用% 挂载点
/dev/sda1      18447100 6422224 11064776   37% /
menwen@menwen:~/$ ./a.out 
done
menwen@menwen:~/$ ll file file.link 
-rwxrwxr-x 2 menwen menwen 7492 1029 11:47 file*
-rwxrwxr-x 2 menwen menwen 7492 1029 11:47 file.link*
menwen@menwen:~/$ df .
文件系统          1K-块    已用     可用 已用% 挂载点
/dev/sda1      18447100 6422224 11064776   37% /

从结果可以看出,调用link函数位file文件创建了一个file.link的硬链接,硬链接为2,并且创建应链接前后,磁盘的可用的空间大小没有变化。这是因为:在数据块存放着许多文件内容,而目录也是文件,它在数据块中只存放的是目录中的目录项的名字和inode编号,file和file.link同为一个目录下的文件,两者的inode存放着同一个inode编号,而inode一直存放着这两个文件的内容在数据块中的地址。所以磁盘可用大小没有变化。

为了删除一个现有的目录项

#include 

int unlink(const char *pathname);
//返回值:若成功,返回0,若出错,返回-1
  • unlink删除目录项,并由pathname所引用文件的链接数减1
  • 如果要解除对文件链接,必须对包含该目录项的目录具有执行权限。
  • 只有当链接计数达到0时,该文件的内容才可被删除,如果内核首先检查是否有进程打开了该文件, 在检查连接数,二者计数都为0时,那么才删除文件的内容。

12.4 函数实例

int main()
{
        int fd;
        if((fd = open("file.link", O_RDONLY)) < 0){//打开file.link文件
                perror("open file.link err");
                exit(1);
        }
        printf("open file.link\n");
        if(unlink("file.link") < 0){//解除file.link
                perror("unlink file.link err");
                exit(1);
        }
        if(unlink("file") < 0){//解除file
                perror("unlink file.link err");
                exit(1);
        }
        sleep(10);

        printf("done\n");
        close(fd);
}

运行结果:

menwen@menwen:~/$ ./a.out &
[1] 20703
menwen@menwen:~/$ open file.link
df .
文件系统          1K-块    已用     可用 已用% 挂载点
/dev/sda1      18447100 6422224 11064776   37% /
menwen@menwen:~/$ done
df .
文件系统          1K-块    已用     可用 已用% 挂载点
/dev/sda1      18447100 6422216 11064784   37% /
[1]+  已完成               ./a.out
menwen@menwen:~/$ ll file file.link
ls: 无法访问'file': 没有那个文件或目录
ls: 无法访问'file.link': 没有那个文件或目录

让程序在后台运行,检查空间没有变化,是因为程序打开了file.link文件,当程序运行完毕后,对比前后磁盘空间变化,解除链接后磁盘可用空间增大8K空间。

12.5 函数remove

unlink函数一般解除文件链接,rmdir函数解除目录链接,而用remove函数解除一个文件或目录的链接,

#include 

int remove(const char *pathname);
//返回值:若成功,返回0,若出错,返回-1

13. 函数rename

使用rename函数对文件重命名

#include 

int rename(const char *oldpath, const char *newpath);
//返回值:若成功,返回0,若出错,返回-1
  • 如若oldpath和newpath引用符号链接,则处理的是符号链接本身。
  • 不能对“.”和“..”重命名
  • 如果oldpath和newpath引用同一文件,则函数不做任何更改而成功返回。

14. 符号链接

符号链接是一个文件的间接引用,与硬链接不同,硬链接直接指向文件inode,符号链接可以避开硬链接的一些限制。

  • 硬链接通常要求链接和文件位于同一文件系统,符号链接一般用于将一个文件,或者整个目录移到系统的另一个位置,类似于Windows系统的快捷方式
  • 只有超级用户才能创建指向目录的硬链接,任何用户都可以创建指向目录的符号链接。

14.1 创建和读取符号链接

医用用symlink函数创建一个符号链接

#include 

int symlink(const char *target, const char *linkpath);
//返回值:若成功,返回0,若出错,返回-1

函数创建一个指向target的新目录项linkpath,在创建此符号链接时,不要求target已经存在,并且target和linkpath并不要位于同一文件系统中。
因为open函数跟随符号链接,所以需要用readlink函数打开链接,读链接中的名字。

#include 

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
//返回值:若成功,返回读取的字节数,若出错,返回-1

readlink函数包含了open、read、close的所有操作,将内容读到buf,并且不以null结尾。

15. 文件的时间

struct stat结构中timespec结构类型按照秒和纳秒定义了时间。

struct timespec {
        long       ts_sec;
        long       ts_nsec;
};

提供了更高精度的时间戳,为了保持兼容性,因此可以用st_atime定义成st_atime.tv_sec。

关于st_atim,st_mtim,st_ctim辨析
- st_atim是最近一次访问(access)文件的时间,如read
- st_mtim是最近一次修改(modification)文件的时间,如write
- st_ctim是最近一次更改inode的时间,如chmod、chown

调用utimes函数可以更新inode的时间

#include 

int utimes(const char *filename, const struct timeval times[2])
//返回值:若成功,返回读取的字节数,若出错,返回-1

16. 函数mkdir和rmdir

#include 
#include 

int mkdir(const char *pathname, mode_t mode);
//返回值:若成功,返回0, 若出错返回-1

mkdir函数可以创建新的目录,其中“.”和“..”自动创建。所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。目录至少要设置一个执行权限位。

#include 

int rmdir(const char *pathname);
//返回值:若成功,返回0, 若出错返回-1

rmdir函数删除一个空目录。空目录只有“.”和“..”这两项目录
若调用这两个函数时目录链接计数为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。

17. 读目录

只有内核才能写目录,一个目录有写和执行权限决定了在该目录下可以新建文件以及删除文件,不代表能否写目录本身。

#include 
#include 

DIR *opendir(const char *name);
DIR *fdopendir(int fd);
//返回值:若成功,返回指针,若出错,返回NULL

struct dirent *readdir(DIR *dirp);
//返回值:若成功,返回指针,若出差或在目录尾,返回NULL

void rewinddir(DIR *dirp);

int closedir(DIR *dirp);
//返回值:若成功,返回0,若出错返回-1

long telldir(DIR *dirp);
//返回值:与drip关联的目录中的当前位置

void seekdir(DIR *dirp, long loc);

一般由opendir和fopendir返回指向DIR结构的指针由另外5个函数调用。

18.函数chdir、fchdir和getcwd

调用chdir和fchdir函数可以更改当前工作目录
每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点。
/etc/passwd中用户登录的第六个字段是用户的起始目录,也就是家目录。当前工作目录是进程的一个属性,起始目录是登录名的一个属性。

#include 

int chdir(const char *path);
int fchdir(int fd);

这两个函数分别用path或打开文件描述符来指定当前工作目录。
chdir是跟随符号链接。
因为当前工作目录是进程的一个属性,所以它只影响调用chdir的进程本身,而不影响其他进程。

#include 

char *getcwd(char *buf, size_t size);
//返回值:若成功,返回buf;若出错,返回NULL

getcwd函数可以得到完整的绝对路径名,有两个参数,一个是缓冲区地址buf,一个是缓冲区的长度size。缓冲区有足够的长度以容纳绝对路径名加上一个终止null字节。
linux系统最长文件名为255字节。

#include 
#include 
#include 

#define MAXSIZE         4096

int main()
{
        char buf[MAXSIZE];
        if(chdir("/") < 0){//改变目录
                perror("chdir err");
                exit(1);
        }
        printf("pwd = %s\n", getcwd(buf, sizeof(buf)));//打印出绝对路径

        return 0;
}

运行结果:

menwen@menwen:~/$ pwd
/home/menwen/APUE_code/FILE_DIR
menwen@menwen:~/$ ./a.out
pwd = /
menwen@menwen:~/$ pwd
/home/menwen/APUE_code/FILE_DIR

该程序并没有改变当前shell的工作目录,因为他俩个是独立的进程。
getcwd可以实现一个应用程序返回它工作的出发点,通过保存更换目录前的绝对路径。fchdir也通过返回打开更换目录之前目录的文件描述符保存路径。

你可能感兴趣的:(Linux,环境编程)