文件系统编程

一、系统调用

            应用程序 -----------+
               |                |
               v                |
             各种库             |
(C/C++标准库、Shell命令和脚本、 |
       X11图形程序及库)         |
               |                |
               v                |
            系统调用 <----------+
(内核提供给外界访问的接口函数,
调用这些函数将使进程进入内核态)
               |
               v
              内核
    (驱动程序、系统功能程序)

1). Unix/Linux大部分系统功能是通过系统调用实现的,如:open/close。
2). Unix/Linux的系统调用已被封装成C函数的形式,但它们并不是标准C的一部分。
3). 标准库函数大部分时间运行在用户态,但部分函数偶尔也会调用系统调用,进入内核态, 如:malloc/free。
4). 程序员自己编写的代码也可以调用系统调用,与操作系统内核交互,进入内核态。如:brk/sbrk/mmap/munmap。
5). 系统调用在内核中实现,其外部接口定义在C库中,该接口的实现借助软中断进入内核。

time命令:测试运行时间
real : 总执行时间
user : 用户空间执行时间
sys : 内核空间执行时间

strace命令:跟踪系统调用

二、一切皆文件

1). Linux环境中的文件具有特别重要的意义,因为它为操作系统服务和设备,提供了一个简单而统一的接口。
在Linux中,(几乎)一切皆文件。
2). 程序完全可以象访问普通磁盘文件一样,访问串行口、网络、打印机或其它设备。
3). 大多数情况下只需要使用五个基本系统调用:open/close/read/write/ioctl,即可实现对各种设备的输入和输出。
4). Linux中的任何对象都可以被视为某种特定类型的文件,可以访问文件的方式访问之。
5). 广义的文件
目录文件
设备文件
A. 控制台:/dev/console
B. 声卡:/dev/audio
C. 标准输入输出:/dev/tty
D. 空设备:/dev/null
例如:

# cat /dev/tty
Hello, World !
Hello, World !
 
# echo Hello, World ! > /dev/tty
Hello, World !
 
# echo Hello, World ! > test.txt
# cat test.txt
Hello, World !
# cat /dev/null > test.txt
# cat test.txt
 
# find / -name perl 2> /dev/null

三、文件相关系统调用

open - 打开/创建文件
creat - 创建空文件
close - 关闭文件
read - 读取文件
write - 写入文件
lseek - 设置读写位置
fcntl - 修改文件属性
unlink - 删除硬链接
rmdir - 删除空目录
remove - 删除硬链接(unlink)或空目录(rmdir)

注意:
1). 如果被unlink/remove删除的是文件的最后一个硬链接,并且没有进程正打开该文件,
那么该文件在磁盘上的存储区域将被立即标记为自由。反之,如果有进程正打开该文件,
那么该文件在磁盘上的存储区域,将在所有进程关闭该文件之后被标记为自由。

  a -> +-----+
X b -> | ... |
X c -> +-----+

2). 如果被unlink/remove删除的是一个软链接文件,那么仅软链接文件本身被删除,其目标不受影响。

       +-----+
  a -> | ... |
       +-----+
       +-----+
X b -> |  a  |
       +-----+
       +-----+
X c -> |  a  |
       +-----+

四、文件描述符

1). 非负的整数。
2). 表示一个打开的文件。
3). 由系统调用(open)返回,被内核空间(后续系统调用)引用。
4). 内核缺省为每个进程打开三个文件描述符:
0 - 标准输入
1 - 标准输出
2 - 标准出错

在unistd.h中被定义为如下三个宏:

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

5). 文件描述符的范围介于0到OPEN_MAX之间,传统Unix中OPEN_MAX宏被定义为64,
现代Linux使用更大的上限,

   #include <unistd.h>
   sysconf (_SC_OPEN_MAX)返回1024。

6). 标准库函数fopen返回的FILE结构体中也包含了文件描述符。

/usr/include/stdio.h
typedef struct _IO_FILE FILE;
/usr/include/libio.h
 struct _IO_FILE {
  ...
  int _fileno; // 文件描述符
  ...
};

五、open/creat/close

#include <fcntl.h>
int open (
    const char* pathname, // 文件路径
    int         flags,    // 状态标志
    mode_t      mode      // 权限模式(仅创建文件有效)
);  // 创建/打开文件时都可用此函数
 
int creat (
    const char* pathname, // 文件路径
    mode_t      mode      // 权限模式
);  // 常用于创建文件
 
int open (
    const char* pathname, // 文件路径
    int         flags     // 状态标志
);  // 常用于打开文件
成功返回文件描述符,失败返回-1。
flags为以下值的位或:
O_RDONLY   - 只读。\
                    |
O_WRONLY   - 只写。 > 只选一个
                    |
O_RDWR     - 读写。/
O_APPEND   - 追加。
O_CREAT    - 创建。不存在即创建(已存在即直接打开,并保留原内容,除非...),有此位mode参数才有效。
O_EXCL     - 排斥。已存在即失败。\
                                  > 只选一个,
O_TRUNC    - 清空。已存在即清空  /  配合O_CREAT使用(有O_WRONLY/O_RDWR)。
O_NOCTTY   - 若pathname指向一个终端设备,则该终端不会成为调用进程的控制终端。
O_NONBLOCK - 非阻塞。若pathname指向FIFO/块/字符文件,则该文件的打开及后续操作均为非阻塞模式。
O_SYNC     - 写同步。write等待数据和属性,被物理地写入底层硬件后再返回。
O_DSYNC    - 数据写同步。write等待数据,被物理地写入底层硬件后再返回。
O_RSYNC    - 读同步。read等待对所访问区域的所有写操作,全部物理地写入底层硬件后,再读取并返回。
O_ASYNC    - 异步读写。当文件描述符可读/写时,向调用进程发送SIGIO信号。

open/creat所返回的一定是当前未被使用的,最小文件描述符。

一个进程可以同时打开的文件描述符个数,受limits.h中定义的OPEN_MAX宏的限制,
POSIX要求不低于16,传统Unix是63,现代Linux是256。

#include <unistd.h>
int close (
    int fd // 文件描述符
);
成功返回0,失败返回-1。

操作系统可通过权限掩码(当前为0022),屏蔽程序所创建文件的某些权限位。如:
0666 (rw-rw-rw-) & ~0022 = 0644 (rw-r--r--)

creat函数是通过调用open实现的。

int creat (const char* pathname, mode_t mode) {
    return open (pathname,
        O_WRONLY | O_CREAT | O_TRUNC, mode);
}

六、write

#include <unistd.h>
ssize_t write (
    int         fd,   // 文件描述符
    const void* buf,  // 缓冲区
    size_t      count // 期望写入的字节数
);
成功返回实际写入的字节数(0表示未写入),失败返回-1。
size_t: unsigned int,无符号整数
ssize_t: int,有符号整数

七、read

#include <unistd.h>
 ssize_t read (
    int    fd,   // 文件描述符
    void*  buf,  // 缓冲区
    size_t count // 期望读取的字节数
);
 
成功返回实际读取的字节数(0表示读到文件尾),
失败返回-1。

八、系统I/O与标准I/O

1). 当系统调用函数被执行时,需要切换用户态和内核态,频繁调用会导致性能损失。
2). 标准库做了必要的优化,内部维护一个缓冲区,只在满足特定条件时才将缓冲区与系统内核同步,
借此降低执行系统调用的频率,减少进程在用户态和内核态之间来回切换的次数,提高运行性能。

九、lseek

1). 每个打开的文件都有一个与其相关的“文件位置”。
2). 文件位置通常是一个非负整数,用以度量从文件头开始计算的字节数。
3). 读写操作都从当前文件位置开始,并根据所读写的字节数,增加文件位置。
4). 打开一个文件时,除非指定了O_APPEND,否则文件位置一律被设为0。
5). lseek函数仅将文件位置记录在内核中,并不引发任何I/O动作。
6). 在超越文件尾的文件位置写入数据,将在文件中形成空洞。
7). 文件空洞不占用磁盘空间,但被算在文件大小内。

#include <sys/types.h>
#include <unistd.h>
off_t lseek (
    int   fd,     // 文件描述符
    off_t offset, // 偏移量
    int   whence  // 起始位置
);
成功返回当前文件位置,失败返回-1。
whence取值:
SEEK_SET - 从文件头(文件的第一个字节)。
SEEK_CUR - 从当前位置(上一次读写的最后一个字节的下一个位置)。
SEEK_END - 从文件尾(文件的最后一个字节的下一个位置)。

范例:seek.c
思考:如何获取文件的大小?
通过lseek(fd,0,SEEK_END)可以获得文件的大小。

十、打开文件的内核数据结构

通过ls -i可查看文件的i节点号。i节点记录了文件的属性和数据在磁盘上的存储位置。目录也是文件,存放路径和i节点号的映射表。

十一、dup/dup2

#include <unistd.h>
int dup (int oldfd);
int dup2 (int oldfd, int newfd);

成功返回文件描述符oldfd的副本,失败返回-1。
1). 复制一个已打开的文件描述符。
2). 返回的一定是当前未被使用的最小文件描述符。
3). dup2可由第二个参数指定描述符的值,若指定描述符已打开,则先关闭之。
4). 所返回的文件描述符副本,与源文件描述符,对应同一个文件表。
注意区分通过dup获得的文件描述符副本,和两次open同一个文件的区别:
dup只复制文件描述符,不复制文件表。

fd1 \
     > 文件表 -> v节点 -> i节点
fd2 /
 
open创建新文件表,并为其分配新文件描述符。
fd1 -> 文件表1 \
                > v节点 -> i节点
fd2 -> 文件表2 /

十二、sync/fsync/fdatasync

1). 大多数磁盘I/O都通过缓冲进行,写入文件其实只是写入缓冲区,直到缓冲区满,才将其排入写队列。
2). 延迟写降低了写操作的次数,提高了写操作的效率,但可能导致磁盘文件与缓冲区数据不同步。
3). sync/fsync/fdatasync用于强制磁盘文件与缓冲区同步。
4). sync将所有被修改过的缓冲区排入写队列即返回,不等待写磁盘操作完成。
5). fsync只针对一个文件,且直到写磁盘操作完成才返回。
6). fdatasync只同步文件数据,不同步文件属性。

#include <unistd.h>
void sync (void);
int fsync (
    int fd
);
成功返回0,失败返回-1。
int fdatasync (
    int fd
);
成功返回0,失败返回-1。

              +-fwrite-> 标准库缓冲 -fflush-+             sync
应用程序内存 -+                             +-> 内核缓冲 -fdatasync-> 磁盘(缓冲)
              +------------write------------+             fsync

十三、fcntl

#include <fcntl.h>
int fcntl (
    int fd,  // 文件描述符
    int cmd, // 操作指令
    ...      // 可变参数,因操作指令而异
);
对fd文件执行cmd操作,某些操作需要提供参数。
  1. 常用形式
#include <fcntl.h>
int fcntl (int fd, int cmd);
int fcntl (int fd, int cmd, long arg);
成功返回值因cmd而异,失败返回-1。

cmd取值:
F_DUPFD - 复制fd为不小于arg的文件描述符。若arg文件描述符已用,该函数会选择比arg大的最小未用值,而非如dup2函数那样关闭之。
F_GETFD - 获取文件描述符标志。
F_SETFD - 设置文件描述符标志。
目前仅定义了一个文件描述符标志位FD_CLOEXEC:
0 - 在通过exec函数族所创建的进程中,该文件描述符依然保持打开。
1 - 在通过exec函数族所创建的进程中,该文件描述符将被关闭。
F_GETFL - 获取文件状态标志,不能获取O_CREAT/O_EXCL/O_TRUNC。
注意:O_RDONLY标志的值为0,不能用位与判断:
if ((flags & O_ACCMODE) == O_RDONLY)// 有只读标志
其它标志可以用位与判断:
if (flags & O_WRONLY)// 有只写标志
F_SETFL - 追加文件状态标志。只能追加O_APPEND/O_NONBLOCK。

  1. 文件锁
#include <fcntl.h>
int fcntl (int fd, int cmd, struct flock* lock);
其中:
struct flock {
    short int l_type;   // 锁的类型:
                        // F_RDLCK/F_WRLCK/F_UNLCK
                        // (读锁/写锁/无锁)
    short int l_whence; // 偏移起点:
                        // SEEK_SET/SEEK_CUR/SEEK_END
                        // (文件头/当前位置/文件尾)
    off_t     l_start;  // 锁区偏移,从l_whence开始
    off_t     l_len;    // 锁区长度,0表示锁到文件尾
    pid_t     l_pid;    // 加锁进程,-1表示自动设置
};

cmd取值:
F_GETLK - 测试lock所表示的锁是否可加,若可加则将lock.l_type置为F_UNLCK, 否则通过lock返回当前锁的信息。
F_SETLK - 设置锁定状态为lock.l_type,成功返回0,失败返回-1。若因其它进程持有锁而导致失败,则errno为EACCES或EAGAIN。
F_SETLKW - 设置锁定状态为lock.l_type,成功返回0,否则一直等待,除非被信号打断返回-1。
前提:A和B是同一个文件上的两个相互重叠的区域。

第一种情况:在写锁区上加写锁 
-------------------------------+-------------------------------
             进程1             |             进程2
-------------------------------+-------------------------------
打开文件,准备写A区            | 打开文件,准备写B区
-------------------------------+-------------------------------
调用fcntl(),给A区加写锁,     |
fcntl()返回成功,A区被加上写锁 |
-------------------------------+-------------------------------
调用write(),写A区             | 调用fcntl(),给B区加写锁,
                               | fcntl()阻塞或返回失败
-------------------------------+-------------------------------
调用fcntl(),解锁A区           | fcntl()返回成功,B区被加上写锁
-------------------------------+-------------------------------
                               | 调用write(),写B区
-------------------------------+-------------------------------
                               | 调用fcntl(),解锁B区
-------------------------------+-------------------------------
关闭文件                       | 关闭文件
-------------------------------+-------------------------------
 
第二种情况:在写锁区上加读锁
-------------------------------+-------------------------------
             进程1             |             进程2
-------------------------------+-------------------------------
打开文件,准备写A区            | 打开文件,准备读B区
-------------------------------+-------------------------------
调用fcntl(),给A区加写锁,     |
fcntl()返回成功,A区被加上写锁 |
-------------------------------+-------------------------------
调用write(),写A区             | 调用fcntl(),给B区加读锁,
                               | fcntl()阻塞或返回失败
-------------------------------+-------------------------------
调用fcntl(),解锁A区           | fcntl()返回成功,B区被加上读锁
-------------------------------+-------------------------------
                               | 调用read(),读B区
-------------------------------+-------------------------------
                               | 调用fcntl(),解锁B区
-------------------------------+-------------------------------
关闭文件                       | 关闭文件
-------------------------------+-------------------------------
 
第三种情况:在读锁区上加写锁
-------------------------------+-------------------------------
             进程1             |             进程2
-------------------------------+-------------------------------
打开文件,准备读A区            | 打开文件,准备写B区
-------------------------------+-------------------------------
调用fcntl(),给A区加读锁,     |
fcntl()返回成功,A区被加上读锁 |
-------------------------------+-------------------------------
调用read(),读A区              | 调用fcntl(),给B区加写锁,
                               | fcntl()阻塞或返回失败
-------------------------------+-------------------------------
调用fcntl(),解锁A区           | fcntl()返回成功,B区被加上写锁
-------------------------------+-------------------------------
                               | 调用write(),写B区
-------------------------------+-------------------------------
                               | 调用fcntl(),解锁B区
-------------------------------+-------------------------------
关闭文件                       | 关闭文件
-------------------------------+-------------------------------
 
第四种情况:在读锁区上加读锁
-------------------------------+-------------------------------
             进程1             |             进程2
-------------------------------+-------------------------------
打开文件,准备读A区            | 打开文件,准备读B区
-------------------------------+-------------------------------
调用fcntl(),给A区加读锁,     |
fcntl()返回成功,A区被加上读锁 |
-------------------------------+-------------------------------
调用read(),读A区              | 调用fcntl(),给B区加读锁,
                               | fcntl()返回成功,B区被加上读锁
-------------------------------+-------------------------------
调用fcntl(),解锁A区           | 调用read(),读B区
-------------------------------+-------------------------------
                               | 调用fcntl(),解锁B区
-------------------------------+-------------------------------
关闭文件                       | 关闭文件
-------------------------------+-------------------------------

1) 既可以锁定整个文件,也可以锁定特定区域。
2) 读锁(共享锁)、写锁(独占锁/排它锁)、解锁。
3) 文件描述符被关闭(进程结束)时,自动解锁。
4) 劝谏锁(协议锁)、强制锁。
5) 文件锁仅在不同进程间起作用。
6) 通过锁同步多个进程对同一个文件的读写访问。

十四、stat/fstat/lstat:获取文件属性。

#include <sys/stat.h>
int stat (
    const char*  path, // 文件路径
    struct stat* buf   // 文件属性
);
int fstat (
    int          fd, // 文件描述符
    struct stat* buf // 文件属性
);
int lstat (
    const char*  path, // 文件路径
    struct stat* buf   // 文件属性
);
成功返回0,失败返回-1。
stat函数跟踪软链接,lstat函数不跟踪软链接。
struct stat {
    dev_t     st_dev;     // 设备ID
    ino_t     st_ino;     // i节点号
    mode_t    st_mode;    // 文件类型和权限
    nlink_t   st_nlink;   // 硬链接数
    uid_t     st_uid;     // 用户ID
    gid_t     st_gid;     // 组ID
    dev_t     st_rdev;    // 特殊设备ID
    off_t     st_size;    // 总字节数
    blksize_t st_blksize; // I/O块字节数
    blkcnt_t  st_blocks;  // 占用块(512字节)数
    time_t    st_atime;   // 最后访问时间
    time_t    st_mtime;   // 最后修改时间
    time_t    st_ctime;   // 最后状态改变时间
};
 
st_mode(0TTSUGO)为以下值的位或:
S_IFDIR           - 目录        \
S_IFREG           - 普通文件     |
S_IFLNK           - 软链接       |
S_IFBLK           - 块设备       > TT (S_IFMT)
S_IFCHR           - 字符设备     |
S_IFSOCK          - Unix域套接字 |
S_IFIFO           - 有名管道    /
--------------------------------
S_ISUID           - 设置用户ID  \
S_ISGID           - 设置组ID     > S
S_ISVTX           - 粘滞        /
--------------------------------
S_IRUSR(S_IREAD)  - 用户可读    \
S_IWUSR(S_IWRITE) - 用户可写     > U (S_IRWXU)
S_IXUSR(S_IEXEC)  - 用户可执行  /
--------------------------------
S_IRGRP           - 同组可读    \
S_IWGRP           - 同组可写     > G (S_IRWXG)
S_IXGRP           - 同组可执行  /
--------------------------------
S_IROTH           - 其它可读    \
S_IWOTH           - 其它可写     > O (S_IRWXO)
S_IXOTH           - 其它可执行  / 
  1. 有关S_ISUID/S_ISGID/S_ISVTX的说明
    1) 执行具有S_ISUID/S_ISGID位的可执行文件所产生的进程,其有效用户ID/有效组ID,
    并不取自由其父进程(比如登录shell)所决定的,实际用户ID/实际组ID,
    而是取自该可执行文件的用户ID/组ID。
    如:/usr/bin/passwd
    2) S_ISUID位对于目录没有意义。
    3) 具有S_ISGID位的目录,在该目录下所创建的文件,继承该目录的组ID,而非其创建者进程的有效组ID。
    4) 具有S_ISVTX位的可执行文件,在其首次执行并结束后,其代码区将被连续地保存在磁盘交换区中,
    而一般磁盘文件中的数据块是离散存放的。因此,下次执行该程序可以获得较快的载入速度。
    现代Unix系统大都采用快速文件系统,已不再需要这种技术。
    5) 具有S_ISVTX位的目录,只有对该目录具有写权限的用户,在满足下列条件之一的情况下,
    才能删除或更名该目录下的文件或子目录:
    A. 拥有此文件或子目录;
    B. 拥有该目录;
    C. 超级用户。

    任何用户都可在该目录下创建文件,任何用户对该目录都享有读/写/执行权限,
    但除root以外的任何用户在该目录下,都只能删除或更名属于自己的文件或子目录。
  2. 常用以下宏辅助分析st_mode
    S_ISDIR() - 是否目录
    S_ISREG() - 是否普通文件
    S_ISLNK() - 是否软链接
    S_ISBLK() - 是否块设备
    S_ISCHR() - 是否字符设备
    S_ISSOCK() - 是否Unix域套接字
    S_ISFIFO() - 是否有名管道

十五、access

#include <unistd.h>
int access (
    const char* pathname, // 文件路径
    int         mode      // 访问模式
);
 
1. 按实际用户ID和实际组ID(而非有效用户ID和有效组ID),进行访问模式测试。
2. 成功返回0,失败返回-1。
3. mode取R_OK/W_OK/X_OK的位或,测试调用进程对该文件,是否可读/可写/可执行,
   或者取F_OK,测试该文件是否存在。

十六、umask

可以用umask命令查看/修改当前shell的文件权限屏蔽字:

#include <sys/stat.h>
 mode_t umask (
    mode_t cmask // 屏蔽字
);

1). 为进程设置文件权限屏蔽字,并返回以前的值,此函数永远成功。
2). cmask由9个权限宏位或组成(直接写八进制整数形式亦可,如0022 - 屏蔽同组和其它用户的写权限):

S_IRUSR(S_IREAD)  - 用户可读
S_IWUSR(S_IWRITE) - 用户可写
S_IXUSR(S_IEXEC)  - 用户可执行
------------------------------
S_IRGRP           - 同组可读
S_IWGRP           - 同组可写
S_IXGRP           - 同组可执行
------------------------------
S_IROTH           - 其它可读
S_IWOTH           - 其它可写
S_IXOTH           - 其它可执行

3). 设上屏蔽字以后,此进程所创建的文件,都不会有屏蔽字所包含的权限。

十七、chmod/fchmod修改文件的权限。

#include <sys/stat.h>
int chmod (
    const char* path, // 文件路径
    mode_t      mode  // 文件权限
);
int fchmod (
    int    fd,  // 文件描述符
    mode_t mode // 文件权限
);
 
成功返回0,失败返回-1。
mode为以下值的位或(直接写八进制整数形式亦可,如07654 - rwSr-sr-T):
S_ISUID           - 设置用户ID
S_ISGID           - 设置组ID
S_ISVTX           - 粘滞
------------------------------
S_IRUSR(S_IREAD)  - 用户可读
S_IWUSR(S_IWRITE) - 用户可写
S_IXUSR(S_IEXEC)  - 用户可执行
------------------------------
S_IRGRP           - 同组可读
S_IWGRP           - 同组可写
S_IXGRP           - 同组可执行
------------------------------
S_IROTH           - 其它可读
S_IWOTH           - 其它可写
S_IXOTH           - 其它可执行
注意:chmod/fchmod同样受umask权限屏蔽字影响。

十八、chown/fchown/lchown修改文件的用户和组。

#include <unistd.h>
int chown (
    const char* path,  // 文件路径
    uid_t       owner, // 用户ID
    gid_t       group  // 组ID
);
int fchown (
    int   fildes, // 文件描述符
    uid_t owner,  // 用户ID
    gid_t group   // 组ID
);
int lchown (
    const char* path,  // 文件路径(不跟踪软链接)
    uid_t       owner, // 用户ID
    gid_t       group  // 组ID
);
成功返回0,失败返回-1。

注意:

  1. 用户ID/组ID取-1表示不修改。
  2. 超级用户可以修改任何文件的用户和组,普通用户只能修改自己文件的用户和组。

十九、truncate/ftruncate 修改文件的长度,截短丢弃,加长添零。

#include <unistd.h>
int truncate (
    const char* path,  // 文件路径
    off_t       length // 文件长度
);
int ftruncate (
    int   fd,    // 文件描述符
    off_t length // 文件长度
);
成功返回0,失败返回-1。

注意:对于文件映射,私有映射(MAP_PRIVATE)将数据写到缓冲区而非文件中,
只有自己可以访问。而对于内存映射,
私有(MAP_PRIVATE)和公有(MAP_SHARED)没有区别,都是仅自己可以访问。

二十、link/unlink/remove/rename

link: 创建文件的硬链接(目录条目)。
unlink: 删除文件的硬链接(目录条目),只有当文件的硬链接数降为0时,文件才会真正被删除。
若该文件正在被某个进程打开,其内容直到该文件被关闭才会被真正删除。
remove: 对文件同unlink,对目录同rmdir (不能删非空目录)。
rename: 修改文件/目录名。

#include <unistd.h>
int link (
    const char* path1, // 文件路径
    const char* path2  // 链接路径
);
int unlink (
    const char* path // 链接路径
);

#include <stdio.h>
int remove (
    const char* pathname // 文件/目录路径
);
int rename (
    const char* old, // 原路径名
    const char* new  // 新路径名
);

成功返回0,失败返回-1。
注意:硬链接只是一个文件名,即目录中的一个条目。软链接则是一个独立的文件,其内容是另一个文件的路径信息。

symlink: 创建软链接。目标文件可以不存在,也可以位于另一个文件系统中。
readlink: 获取软链接文件本身(而非其目标)的内容。
open:不能打开软链接文件本身。

#include <unistd.h>
int symlink (
    const char* oldpath, // 文件路径(可以不存在)
    const char* newpath  // 链接路径
);
成功返回0,失败返回-1。
ssize_t readlink (
    const char* restrict path,   // 软链接文件路径
    char* restrict       buf,    // 缓冲区
    size_t               bufsize // 缓冲区大小
                                 // 以字节为单位,不含结尾空字符
);

成功返回实际拷入缓冲区buf中软链接文件内容的字节数,失败返回-1。
注意:readlink在将软链接文件内容拷入缓冲区buf时,不负责追加结尾空字符。

二十二、mkdir/rmdir

mkdir: 创建一个空目录。
rmdir: 删除一个空目录。

#include <sys/stat.h>
int mkdir (
    const char* path, // 目录路径
    mode_t      mode  // 访问权限,
                      // 目录的执行权限(x)表示可进入
);
 
#include <unistd.h>
int rmdir (
    const char* path // 目录路径
); 
成功返回0,失败返回-1。

二十三、chdir/fchdir/getcwd

chdir/fchdir: 更改当前工作目录。工作目录是进程的属性,只影响调用进程本身。
getcwd: 获取当前工作目录。

#include <unistd.h>
int chdir (
    const char* path // 工作目录路径
);
int fchdir (
    int fildes // 工作目录描述符(由open函数返回)
);
成功返回0,失败返回-1。
 
 char* getcwd (
    char*  buf, // 缓冲区
    size_t size // 缓冲区大小,
                // 以字节为单位,含结尾空字符
);
 
成功返回当前工作目录字符串指针,失败返回NULL。
 
注意:getcwd在将当前工作目录字符串拷入缓冲区buf时,
会自动追加结尾空字符。

二十四、opendir/fdopendir/closedir/readdir/rewinddir/telldir/seekdir

opendir/fdopendir: 打开目录流。
closedir: 关闭目录流。
readdir: 读取目录流。
rewinddir: 复位目录流。
telldir: 获取目录流位置。
seekdir: 设置目录流位置。

#include <sys/types.h>
#include <dirent.h>
DIR* opendir (
    const char* name // 目录路径
);
 
DIR* fdopendir (
    int fd // 目录描述符(由open函数返回)
);
成功返回目录流指针,失败返回NULL。
 
int closedir (
    DIR* dirp // 目录流指针
);
成功返回0,失败返回-1。
 
struct dirent* readdir (
    DIR* dirp // 目录流指针
);
成功返回下一个目录条目结构体的指针,
到达目录尾(不置errno)或失败(设置errno)返回NULL。
 
struct dirent {
    ino_t          d_ino;       // i节点号
    off_t          d_off;       // 下一条目在目录流中的位置
    unsigned short d_reclen;    // 记录长度
    unsigned char  d_type;      // 文件类型
    char           d_name[256]; // 文件名
};
 
d_type取值:
DT_DIR     - 目录
DT_REG     - 普通文件
DT_LNK     - 软链接
DT_BLK     - 块设备
DT_CHR     - 字符设备
DT_SOCK    - Unix域套接字
DT_FIFO    - 有名管道
DT_UNKNOWN - 未知

void rewinddir (
    DIR* dirp // 目录流指针
);
long telldir (
    DIR* dirp // 目录流指针
);
 成功返回目录流位置,失败返回-1。
 
void seekdir (
    DIR* dirp,  // 目录流指针
    long offset // 目录流位置
);
 
目录流:
 
            +-----------------------+           +-----------------------+
            |                       v           |                       v
+-------+---|---+-----+-------+     +-------+---|---+-----+-------+     +-------
| d_ino | d_off | ... | a.txt | ... | d_ino | d_off | ... | b.txt | ... | d_ino
+-------+-------+-----+-------+     +-------+-------+-----+-------+     +-------
^                                   ^
|          -- readdir() ->          |

转载于:https://www.cnblogs.com/ONLYONEING/p/4423578.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值