==================== 第四课 文件系统(下) ==================== 一、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_GETFL - 获取文件状态标志。 不能获取O_CREAT/O_EXCL/O_TRUNC。 F_SETFL - 追加文件状态标志。 只能追加O_APPEND/O_NONBLOCK。 F_GETFD - 获取文件描述符标志。 F_SETFD - 设置文件描述符标志。 目前仅定义了一个文件描述符标志位(file descriptor FLAG)FD_CLOEXEC: 0 - 在通过execve()函数所创建的进程中, 该文件描述符依然保持打开。 1 - 在通过execve()函数所创建的进程中, 该文件描述符将被关闭。 范例:dup.c、flags.c/* * fcntl函数练习 * */ #include <stdio.h> #include <string.h> #include <fcntl.h> int main (void) { int fd1 = open ("dup1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd1 == -1) { perror ("open"); return -1; } printf ("fd1 = %d\n", fd1); int fd2 = open ("dup2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd2 == -1) { perror ("open"); return -1; } printf ("fd2 = %d\n", fd2); /* int fd3 = dup2 (fd1, fd2); if (fd3 == -1) { perror ("dup2"); return -1; } */ int fd3 = fcntl (fd1, F_DUPFD, fd2); if (fd3 == -1) { perror ("fcntl"); return -1; } printf ("fd3 = %d\n", fd3); const char* text = "123"; if (write (fd1, text, strlen (text) * sizeof (text[0])) == -1) { perror ("write"); return -1; } text = "456"; if (write (fd2, text, strlen (text) * sizeof (text[0])) == -1) { perror ("write"); return -1; } text = "789"; if (write (fd3, text, strlen (text) * sizeof (text[0])) == -1) { perror ("write"); return -1; } close (fd3); close (fd2); close (fd1); return 0; }
/* * fcntl()练习 读写文件状态标志 * 获取文件文件描述符表(file descriptor table)里文件状态标志flag * */ #include <stdio.h> #include <fcntl.h> void pflags (int flags) { printf ("文件状态标志(%08X):", flags); struct { int flag; const char* desc; } flist[] = { O_RDONLY, "O_RDONLY", O_WRONLY, "O_WRONLY", O_RDWR, "O_RDWR", O_APPEND, "O_APPEND", O_CREAT, "O_CREAT", O_EXCL, "O_EXCL", O_TRUNC, "O_TRUNC", O_NOCTTY, "O_NOCTTY", O_NONBLOCK, "O_NONBLOCK", O_SYNC, "O_SYNC", O_DSYNC, "O_DSYNC", O_RSYNC, "O_RSYNC", O_ASYNC, "O_ASYNC" }; size_t i; int first = 1; for (i = 0; i < sizeof (flist) / sizeof (flist[0]); i++) if (flags & flist[i].flag) { printf ("%s%s", first ? "" : " | ", flist[i].desc); first = 0; } printf ("\n"); } int main (void) { int fd = open ("flags.txt", O_WRONLY | O_CREAT | O_TRUNC | O_ASYNC, 0644); if (fd == -1) { perror ("open"); return -1; } int flags = fcntl (fd, F_GETFL); if (flags == -1) { perror ("fcntl"); return -1; } pflags (flags); // 不能获取O_CREATE/O_EXCL/O_TRUNC if (fcntl (fd, F_SETFL, O_RDWR | O_APPEND | O_NONBLOCK) == -1) { perror ("fcntl"); return -1; } if ((flags = fcntl (fd, F_GETFL)) == -1) { perror ("fcntl"); return -1; } pflags (flags); // 只能追加O_APPEND/O_NONBLOCK close (fd); return 0; }
2. 文件锁
~~~~~~~~~
#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 (文件头/当前位置/文件尾)
// 这里"偏移起点"作为锁区起始位置l_start的0位置参考点使用,用l_start相对l_whence的位置表明锁区位置
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。
1) 既可以锁定整个文件,也可以锁定特定区域。
2) 读锁(共享锁)、写锁(独占锁/排它锁)、解锁。
3) 文件描述符被关闭(进程结束)时,自动解锁。
4) 劝谏锁(协议锁)、强制锁。
范例:lock1.c、lock2.c/* * fcntl()练习 * 协议锁练习 * */ #include <stdio.h> #include <string.h> #include <fcntl.h> // 加读锁 int rlock (int fd, off_t start, off_t len, int wait) { struct flock lock; lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock); } // 加写锁 int wlock (int fd, off_t start, off_t len, int wait) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock); } // 解锁 int ulock (int fd, off_t start, off_t len) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, F_SETLK, &lock); } int main (void) { printf ("进程标识(PID):%u\n", getpid ()); int fd = open ("lock.txt", O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror ("open"); return -1; } const char* text = "ABCDEFGHIJKLMNOPQR"; if (write (fd, text, strlen (text) * sizeof (text[0])) == -1) { perror ("write"); return -1; } // 对EFGH加读锁 printf ("对EFGH加读锁"); if (rlock (fd, 4, 4, 0) == -1) { printf ("失败:%m\n"); return -1; } printf ("成功!\n"); // 对MNOP加写锁 printf ("对MNOP加写锁"); if (wlock (fd, 12, 4, 0) == -1) { printf ("失败:%m\n"); return -1; } printf ("成功!\n"); printf ("按<回车>,解锁MN..."); getchar (); // 解锁NM ulock (fd, 12, 2); printf ("按<回车>,解锁EFGH..."); getchar (); // 解锁EFGH ulock (fd, 4, 4); close (fd); return 0; }
/*fcntl()练习 * 协议锁 */ #include <stdio.h> #include <fcntl.h> // 打印锁信息 void plock (struct flock lock) { if (lock.l_type == F_UNLCK) printf ("没有锁。\n"); else { printf ("%d进程", lock.l_pid); switch (lock.l_whence/*l_whence是锁区的参考位置*/) { case SEEK_SET: printf ("在距文件头"); break; case SEEK_CUR: printf ("在距当前位置"); break; case SEEK_END: printf ("在距文件尾"); break; } printf ("%d字节处,为%d字节加了", lock.l_start, lock.l_len); switch (lock.l_type) { case F_RDLCK: printf ("读锁。\n"); break; case F_WRLCK: printf ("写锁。\n"); break; } } } // 读锁测试 int rtest (int fd, off_t start, off_t len) { struct flock lock; lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; if (fcntl (fd, F_GETLK, &lock) == -1) return -1; plock (lock); return 0; } // 写锁测试 int wtest (int fd, off_t start, off_t len) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; if (fcntl (fd, F_GETLK, &lock) == -1) return -1; plock (lock); return 0; } // 加读锁 int rlock (int fd, off_t start, off_t len, int wait) { struct flock lock; lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock); } // 加写锁 int wlock (int fd, off_t start, off_t len, int wait) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock); } // 解锁 int ulock (int fd, off_t start, off_t len) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, F_SETLK, &lock); } int main (void) { int fd = open ("lock.txt", O_RDWR); if (fd == -1) { perror ("open"); return -1; } // 对CDEF做读锁测试 printf ("对CDEF做读锁测试。"); if (rtest (fd, 2, 4) == -1) { printf ("失败:%m\n"); return -1; } // 对CDEF加读锁 printf ("对CDEF加读锁"); if (rlock (fd, 2, 4, 0) == -1) printf ("失败:%m\n"); else { printf ("成功!\n"); ulock (fd, 2, 4); } // 对CDEF做写锁测试 printf ("对CDEF做写锁测试。"); if (wtest (fd, 2, 4) == -1) { printf ("失败:%m\n"); return -1; } // 对CDEF加写锁 printf ("对CDEF加写锁"); if (wlock (fd, 2, 4, 0) == -1) printf ("失败:%m\n"); else { printf ("成功!\n"); ulock (fd, 2, 4); } // 对KLMN做读锁测试 printf ("对KLMN做读锁测试。"); if (rtest (fd, 10, 4) == -1) { printf ("失败:%m\n"); return -1; } // 对KLMN加读锁 printf ("对KLMN加读锁"); if (rlock (fd, 10, 4, 0) == -1) printf ("失败:%m\n"); else { printf ("成功!\n"); ulock (fd, 10, 4); } // 对KLMN做写锁测试 printf ("对KLMN做写锁测试。"); if (wtest (fd, 10, 4) == -1) { printf ("失败:%m\n"); return -1; } // 对KLMN加写锁 printf ("对KLMN加写锁"); if (wlock (fd, 10, 4, 0) == -1) printf ("失败:%m\n"); else { printf ("成功!\n"); ulock (fd, 10, 4); } printf ("等待KLMN上的写锁被解除...\n"); // 对KLMN加写锁 printf ("对KLMN加写锁"); if (wlock (fd, 10, 4, 1) == -1) printf ("失败:%m\n"); else { printf ("成功!\n"); ulock (fd, 10, 4); } close (fd); return 0; }
5) 文件锁仅在不同进程间起作用。
6) 通过锁同步多个进程对同一个文件的读写访问。
范例:wlock.c、rlock.c/*文件名: rlock.c */ #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> // 读锁测试 // 返回值: // 1 - 可加读锁 // 0 - 不可加读锁 // -1 - 系统错误 int rtest (int fd, off_t start, off_t len) { struct flock lock; lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; if (fcntl (fd, F_GETLK, &lock) == -1) return -1; if (lock.l_type == F_UNLCK) return 1; return 0; } // 加读锁 int rlock (int fd, off_t start, off_t len, int wait) { struct flock lock; lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock); } // 解锁 int ulock (int fd, off_t start, off_t len) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, F_SETLK, &lock); } int main (int argc, char* argv[]) { int lock = 0; if (argc > 1) if (! strcmp (argv[1], "-l")) lock = 1; else goto usage; int fd = open ("wlock.txt", O_RDONLY); if (fd == -1) { perror ("open"); return -1; } if (lock) { // 基于锁测试的非阻塞模式 /* int unlock = 0; do { if ((unlock = rtest (fd, 0, 0)) == -1) { perror ("rtest"); return -1; } if (! unlock) { printf ("该文件已被锁定,稍后再试...\n"); // 空闲处理 // ... } } while (! unlock); if (rlock (fd, 0, 0, 0) == -1) { perror ("rlock"); return -1; } */ // 基于锁失败的非阻塞模式 /* while (rlock (fd, 0, 0, 0) == -1) { if (errno != EACCES && errno != EAGAIN) { perror ("rlock"); return -1; } printf ("该文件已被锁定,稍后再试...\n"); // 空闲处理 // ... } */ // 阻塞模式 if (rlock (fd, 0, 0, 1) == -1) { perror ("rlock"); return -1; } } char buf[1024]; ssize_t readed; while ((readed = read (fd, buf, sizeof (buf))) > 0) write (STDOUT_FILENO, buf, readed); printf ("\n"); if (readed == -1) { perror ("read"); return -1; } if (lock) if (ulock (fd, 0, 0) == -1) { perror ("ulock"); return -1; } close (fd); return 0; usage: fprintf (stderr, "用法:%s [-l]\n", argv[0]); return -1; }
/*文件名:wlock.c */ #include <stdio.h> #include <string.h> #include <fcntl.h> #include <errno.h> // 写锁测试 // 返回值: // 1 - 可加写锁 // 0 - 不可加写锁 // -1 - 系统错误 int wtest (int fd, off_t start, off_t len) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; if (fcntl (fd, F_GETLK, &lock) == -1) return -1; if (lock.l_type == F_UNLCK) return 1; return 0; } // 加写锁 int wlock (int fd, off_t start, off_t len, int wait) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock); } // 解锁 int ulock (int fd, off_t start, off_t len) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = start; lock.l_len = len; lock.l_pid = -1; return fcntl (fd, F_SETLK, &lock); } int main (int argc, char* argv[]) { if (argc < 2) goto usage; int lock = 0; if (argc > 2) if (! strcmp (argv[2], "-l")) lock = 1; else goto usage; int fd = open ("wlock.txt", O_WRONLY | O_CREAT | O_APPEND, 0644); if (fd == -1) { perror ("open"); return -1; } if (lock) { // 基于锁测试的非阻塞模式 /* int unlock = 0; do { if ((unlock = wtest (fd, 0, 0)) == -1) { perror ("wtest"); return -1; } if (! unlock) { printf ("该文件已被锁定,稍后再试...\n"); // 空闲处理 // ... } } while (! unlock); if (wlock (fd, 0, 0, 0) == -1) { perror ("wlock"); return -1; } */ // 基于锁失败的非阻塞模式 /* while (wlock (fd, 0, 0, 0) == -1) { if (errno != EACCES && errno != EAGAIN) { perror ("wlock"); return -1; } printf ("该文件已被锁定,稍后再试...\n"); // 空闲处理 // ... } */ // 阻塞模式 if (wlock (fd, 0, 0, 1) == -1) { perror ("wlock"); return -1; } } size_t i, len = strlen (argv[1]); for (i = 0; i < len; i++) { if (write (fd, &argv[1][i], sizeof (argv[1][i])) == -1) { perror ("write"); return -1; } sleep (1); } if (lock) if (ulock (fd, 0, 0) == -1) { perror ("ulock"); return -1; } close (fd); return 0; usage: fprintf (stderr, "用法:%s <字符串> [-l]\n", argv[0]); return -1; }
三、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 - 字符设备 | //用了6个二进制位来代表7个文件类型
S_IFSOCK - Unix域套接字 | //它们是000000/000001/000010/000100/001000/010000/100000
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位的目录,
其中的文件或目录除root外,
只有其属主可以删除。
3) 具有S_ISGID位的目录,
在该目录下所创建的文件,继承该目录的属组ID,
而非其创建者进程的有效组ID。
4) 具有S_ISVTX位的可执行文件,
在其首次执行并结束后,
其代码区将被连续地保存在磁盘交换区中,
而一般磁盘文件中的数据块是离散存放的。
因此,下次执行该程序可以获得较快的载入速度。
现代Unix系统大都采用快速文件系统,
已不再需要这种技术。
5) 具有S_ISVTX位的目录,
只有对该目录具有写权限的用户,
在满足下列条件之一的情况下,
才能删除或更名该目录下的文件或目录:
A. 拥有此文件;
B. 拥有此目录;
C. 是超级用户。
如:/tmp
任何用户都可在该目录下创建文件,
任何用户对该目录都享有读/写/执行权限,
但除root以外的任何用户在目录下,
都只能删除或更名属于自己的文件。
2. 常用以下宏辅助分析st_mode
S_ISDIR() - 是否目录
S_ISREG() - 是否普通文件
S_ISLNK() - 是否软链接
S_ISBLK() - 是否块设备
S_ISCHR() - 是否字符设备
S_ISSOCK() - 是否Unix域套接字
S_ISFIFO() - 是否有名管道
范例:stat.c/* * 用函数stat()获得文件信息*/ #include <stdio.h> #include <string.h> #include <sys/stat.h> #include <time.h> const char* mtos (mode_t m) { static char s[11]; if (S_ISDIR (m)) strcpy (s, "d"); else if (S_ISLNK (m)) strcpy (s, "l"); else if (S_ISBLK (m)) strcpy (s, "b"); else if (S_ISCHR (m)) strcpy (s, "c"); else if (S_ISSOCK (m)) strcpy (s, "s"); else if (S_ISFIFO (m)) strcpy (s, "p"); else strcpy (s, "-"); strcat (s, m & S_IRUSR ? "r" : "-"); strcat (s, m & S_IWUSR ? "w" : "-"); strcat (s, m & S_IXUSR ? "x" : "-"); strcat (s, m & S_IRGRP ? "r" : "-"); strcat (s, m & S_IWGRP ? "w" : "-"); strcat (s, m & S_IXGRP ? "x" : "-"); strcat (s, m & S_IROTH ? "r" : "-"); strcat (s, m & S_IWOTH ? "w" : "-"); strcat (s, m & S_IXOTH ? "x" : "-"); if (m & S_ISUID) s[3] = (s[3] == 'x' ? 's' : 'S'); if (m & S_ISGID) s[6] = (s[6] == 'x' ? 's' : 'S'); if (m & S_ISVTX) s[9] = (s[9] == 'x' ? 't' : 'T'); return s; } const char* ttos (time_t t) { static char s[20]; struct tm* lt = localtime (&t); sprintf (s, "%04d-%02d-%02d %02d:%02d:%02d", lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec); return s; } int main (int argc, char* argv[]) { if (argc < 2) goto usage; struct stat st; if (argc < 3) { if (stat (argv[1], &st) == -1) { perror ("stat"); return -1; } } else if (! strcmp (argv[2], "-l")) { if (lstat (argv[1], &st) == -1) { perror ("lstat"); return -1; } } else goto usage; printf (" 设备ID:%u\n", st.st_dev); printf (" i节点号:%u\n", st.st_ino); //printf (" 模式:%#o\n", st.st_mode); printf (" 模式:%s\n", mtos (st.st_mode)); printf (" 硬链接数:%u\n", st.st_nlink); printf (" 属主ID:%u\n", st.st_uid); printf (" 属组ID:%u\n", st.st_gid); printf (" 特殊设备ID:%u\n", st.st_rdev); printf (" 总字节数:%u\n", st.st_size); printf (" I/O块字节数:%u\n", st.st_blksize); printf ("占用块(512字节)数:%u\n", st.st_blocks); printf (" 最后访问时间:%s\n", ttos (st.st_atime)); printf (" 最后修改时间:%s\n", ttos (st.st_mtime)); printf (" 最后状态改变时间:%s\n", ttos (st.st_ctime)); return 0; usage: fprintf (stderr, "用法:%s <文件> [-l]\n", argv[0]); return -1; }
四、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,测试该文件是否存在。
范例:access.c/* * access()函数练习 * 按照进程实际用户ID和实际组ID进行访问模式测试 * */ #include <stdio.h> #include <unistd.h> int main (int argc, char* argv[]) { if (argc < 2) { fprintf (stderr, "用法:%s <文件>\n", argv[0]); return -1; } printf ("文件%s", argv[1]); if (access (argv[1], F_OK) == -1) printf ("不存在(%m)。\n"); else { if (access (argv[1], R_OK) == -1) printf ("不可读(%m),"); else printf ("可读,"); if (access (argv[1], W_OK) == -1) printf ("不可写(%m),"); else printf ("可写,"); if (access (argv[1], X_OK) == -1) printf ("不可执行(%m)。\n"); else printf ("可执行。\n"); } return 0; }
五、umask
---------
可以用umask命令查看/修改当前shell的文件权限屏蔽字:
# umask
0022
# umask 0033
# umask
0033
#include <sys/stat.h>
mode_t umask (
mode_t cmask // 屏蔽字
);
1. 为进程设置文件权限屏蔽字,并返回以前的值,
此函数永远成功。
2. cmask由9个权限宏位或组成(直接写八进制整数形式亦可,
如022 - 屏蔽属组和其它用户的写权限):
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. 设上屏蔽字以后,此进程所创建的文件,
都不会有屏蔽字所包含的权限。
范例:umask.c#include <stdio.h> #include <fcntl.h> #include <sys/stat.h> int main (void) { // mode_t old = umask (0333); mode_t old = umask ( S_IWUSR | S_IXUSR | S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH); int fd = open ("umask.txt", O_RDWR | O_CREAT | O_TRUNC, 0777); if (fd == -1) { perror ("open"); return -1; } close (fd); umask (old); return 0; }
六、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.c/* * chmod()函数练习 * */ #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> int main (void) { int fd = open ("chmod.txt", O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror ("open"); return -1; } // if (fchmod (fd, 07654) == -1) { if (fchmod (fd, S_ISUID | S_ISGID | S_ISVTX | S_IRUSR | S_IWUSR | S_IRGRP | S_IXGRP | S_IROTH) == -1) { perror ("fchmod"); return -1; } close (fd); return 0; }
七、chown/fchown/lchown
-----------------------
# chown <uid>:<gid> <file>
修改文件的属主和属组。
#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取-1表示不修改。
2. 超级用户进程可以修改文件的属主和属组,
普通进程必须拥有该文件才可以修改其属主和属组。
八、truncate/ftruncate
----------------------
修改文件的长度,截短丢弃,加长添零。
#include <unistd.h>
int truncate (
const char* path, // 文件路径
off_t length // 文件长度
);
int ftruncate (
int fd, // 文件描述符
off_t length // 文件长度
);
成功返回0,失败返回-1。
范例:trunc.c、mmap.c/*文件名: trunc.c * truncate()练习 * * mmap()/mumap()练习 虚拟内存映射到磁盘文件 * */ #include <stdio.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> int main (void) { const char* text = "Hello, World !"; size_t size = (strlen (text) + 1) * sizeof (text[0]); int fd = open ("trunc.txt", O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror ("open"); return -1; } if (ftruncate (fd, size) == -1) { perror ("ftruncate"); return -1; } void* map = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED/*MAP_PRIVATE*/, fd, 0); if (map == MAP_FAILED) { perror ("mmap"); return -1; } memcpy (map, text, size); printf ("%s\n", map); munmap (map, size); close (fd); return 0; }
/*mmap() mumap()练习 * 虚拟内存映射-映射到磁盘文件 * */ #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/mman.h> int main (void) { int fd = open ("trunc.txt", O_RDONLY); if (fd == -1) { perror ("open"); return -1; } struct stat st; if (fstat (fd, &st) == -1) { perror ("fstat"); return -1; } void* map = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (map == MAP_FAILED) { perror ("mmap"); return -1; } printf ("%s\n", map); munmap (map, st.st_size); close (fd); return 0; }
注意:对于虚拟内存到磁盘文件的映射,私有映射(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
--------------------
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。
范例:slink.c/* * syslink()练习 * */ #include <stdio.h> #include <limits.h> int main (int argc, char* argv[]) { if (argc < 3) { fprintf (stderr, "用法:%s <文件> <软链接>\n", argv[0]); return -1; } if (symlink (argv[1], argv[2]) == -1) { perror ("symlink"); return -1; } char slink[PATH_MAX+1] = {0}; if (readlink (argv[2], slink, sizeof (slink) - sizeof (slink[0])) == -1) { perror ("readlink"); return -1; } printf ("%s是%s的软链接。\n", argv[2], slink); return 0; }
十一、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。
范例:dir.c/* * getcwd();chdir();mkdir();练习 * */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <limits.h> int main (void) { char cwd[PATH_MAX+1]; if (! getcwd (cwd, sizeof (cwd))) { perror ("getcwd"); return -1; } printf ("当前工作目录:%s\n", cwd); if (mkdir ("work", 0755) == -1) { perror ("mkdir"); return -1; } if (chdir ("work") == -1) { perror ("chdir"); return -1; } if (! getcwd (cwd, sizeof (cwd))) { perror ("getcwd"); return -1; } printf ("当前工作目录:%s\n", cwd); if (mkdir ("empty", 0755) == -1) { perror ("mkdir"); return -1; } if (rmdir ("empty") == -1) { perror ("rmdir"); return -1; } if (rmdir ("../work") == -1) { perror ("rmdir"); return -1; } return 0; }
十三、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 - 未知
范例:list.c/* * readdir()练习 * * 查看目录文件内容*/ #include <stdio.h> #include <dirent.h> #include <errno.h> int main (int argc, char* argv[]) { if (argc < 2) { fprintf (stderr, "用法:%s <目录>\n", argv[0]); return -1; } DIR* dp = opendir (argv[1]); if (! dp) { perror ("opendir"); return -1; } errno = 0; struct dirent* de; for (de = readdir (dp); de; de = readdir (dp)) { switch (de -> d_type) { case DT_DIR: printf (" 目录:"); break; case DT_REG: printf (" 普通文件:"); break; case DT_LNK: printf (" 软链接:"); break; case DT_BLK: printf (" 块设备:"); break; case DT_CHR: printf (" 字符设备:"); break; case DT_SOCK: printf ("Unix域套接字:"); break; case DT_FIFO: printf (" 有名管道:"); break; default: printf (" 未知:"); break; } printf ("%s\n", de -> d_name); } if (errno) { perror ("readdir"); return -1; } closedir (dp); return 0; }
代码:tree.c/* 文件名:tree.c * 打印指定目录的目录树 * */ #include <stdio.h> #include <dirent.h> #include <errno.h> /* #include <limits.h> */ int tree (const char* dir, size_t depth) { DIR* dp = opendir (dir); if (! dp) { perror ("opendir"); return -1; } if (chdir (dir) == -1) { perror ("chdir"); return -1; } errno = 0; struct dirent* de; for (de = readdir (dp); de; de = readdir (dp)) if (de -> d_type != DT_DIR) printf ("%*s%s\n", depth * 2, "", de -> d_name); else if (strcmp (de -> d_name, ".") && strcmp (de -> d_name, "..")) { printf ("%*s%s/\n", depth * 2, "", de -> d_name); /* char subdir[PATH_MAX+1]; sprintf (subdir, "%s/%s", dir, de -> d_name); if (tree (subdir, depth + 1) == -1) return -1; */ if (tree (de -> d_name, depth + 1) == -1) return -1; } if (errno) { perror ("readdir"); return -1; } if (chdir ("..") == -1) { perror ("chdir"); return -1; } closedir (dp); return 0; } int main (int argc, char* argv[]) { if (argc < 2) { fprintf (stderr, "用法:%s <目录>\n", argv[0]); return -1; } return tree (argv[1], 0); }
void rewinddir (
DIR* dirp // 目录流指针
);
long telldir (
DIR* dirp // 目录流指针
);
成功返回目录流的当前位置,失败返回-1。
void seekdir (
DIR* dirp, // 目录流指针
long offset // 位置偏移量
);
目录流是磁盘上目录文件里各个条目的整体,每个条目都是一个具有下图结构:
(p.s.文件流是磁盘上文件里各字节数据的整体。)
+-----------------------+ +-----------------------+
| v | v
+-------+---|---+-----+-------+ +-------+---|---+-----+-------+ +-------
| d_ino | d_off | ... | a.txt | ... | d_ino | d_off | ... | b.txt | ... | d_ino
+-------+-------+-----+-------+ +-------+-------+-----+-------+ +-------
^ ^
| -- readdir() -> |
范例:seek.c/*文件名:seek.c * seekdir()/rewinddir()/telldir()练习 */ #include <stdio.h> #include <dirent.h> #include <errno.h> int main (int argc, char* argv[]) { if (argc < 2) { fprintf (stderr, "用法:%s <目录>\n", argv[0]); return -1; } DIR* dp = opendir (argv[1]); if (! dp) { perror ("opendir"); return -1; } /* seekdir (dp, 6373414); rewinddir (dp); */ long offset = telldir (dp); if (offset == -1) { perror ("telldir"); return -1; } errno = 0; struct dirent* de; for (de = readdir (dp); de; de = readdir (dp)) { printf ("[%010d %010d] ", offset, de -> d_off); switch (de -> d_type) { case DT_DIR: printf (" 目录:"); break; case DT_REG: printf (" 普通文件:"); break; case DT_LNK: printf (" 软链接:"); break; case DT_BLK: printf (" 块设备:"); break; case DT_CHR: printf (" 字符设备:"); break; case DT_SOCK: printf ("Unix域套接字:"); break; case DT_FIFO: printf (" 有名管道:"); break; default: printf (" 未知:"); break; } printf ("%s\n", de -> d_name); if ((offset = telldir (dp)) == -1) { perror ("telldir"); return -1; } } if (errno) { perror ("readdir"); return -1; } closedir (dp); return 0; }
Unix C, Day04
最新推荐文章于 2024-09-14 00:00:08 发布