uc笔记05---sync/fsync/fdatasync,fcntl,文件锁,stat/fstat/lstat
2015-09-30 16:58
746 查看
1. 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
2. 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 - 获取文件描述符标志。
目前仅定义了一个文件描述符标志位 FD_CLOEXEC;
0 - 在通过 execve() 函数所创建的进程中,该文件描述符依然保持打开。
1 - 在通过 execve() 函数所创建的进程中,该文件描述符将被关闭。
F_SETFD - 设置文件描述符标志。
arg 只能取 FD_CLOEXEC (调用 exec 族函数后关闭该 fd)。
F_GETFL - 获取文件模式;不能获取 O_CREAT、O_EXCL 和 O_TRUNC。
F_SETFL - 追加文件模式;arg 只能取 O_APPEND 和 O_NONBLOCK。
范例:dup.c
#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);
/* fd1 复制给 fd2,若 fd2 已经打开,则关闭,返回 fd2
int fd3 = dup2 (fd1, fd2); // fd1, fd2, fd3 分别为:3,4,4(对应同一个文件表)
if (fd3 == -1) {
perror ("dup2");
return -1;
}
*/
// fd1 复制给 fd2,由于 fd2 已经打开,所以选择一个新的:
int fd3 = fcntl (fd1, F_DUPFD, fd2); // fd1, fd2, fd3 分别为:3,4,5(3 和 5 对应同一个文件表)
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;
}
分析:
int fd3 = dup2 (fd1, fd2); fd2 复制给 fd1,如果 fd2 已经打开,则先关闭 fd2,
然后再进行复制,那么 fd2 和 fd3 的文件描述符一样;且 fd1 fd2 fd3 三个文件的内容一样;
上例中,fd1 fd2 fd3 内容都为:123456789
int fd3 = fcntl (fd1, F_DUPFD, fd2); 同样把 fd2 复制给 fd1,如果发现 fd2 打开,
则另外再找一个最小文件描述符给 fd3,所以 fd1 fd2 fd3 文件描述符分别为:3 4 5;
同时,fd1 和 fd3 的内容一样,为:123789,fd2 内容为:456;
范例:flags.c
#include <stdio.h>
#include <fcntl.h>
void pflags (int flags) {
printf ("文件模式(%08X):", flags); // %08x 十六进制
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); // 输出类似格式:O_WRONLY | O_ASYNC
first = 0;
}
printf ("\n");
}
int main (void) {
int fd = open ("flags.txt", O_WRONLY | O_CREAT | 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 不能被 F_GETFL 获取
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 可以被 F_SETFL 追加
close (fd);
return 0;
}
3. 文件锁
#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。
1) 既可以锁定整个文件,也可以锁定特定区域。
2) 读锁 (共享锁)、写锁 (独占锁/排它锁)、解锁。
图示:rwlock.bmp、flock.bmp
3) 文件描述符被关闭 (进程结束) 时,自动解锁。
4) 劝谏锁 (协议锁)、强制锁。
举例:两个进程对同一个文件操作 nolock.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char* argv[]) {
// 从命令行输入要打印的字符串
if(argc < 2)
goto usage;
// 字符串写入这个文件中
int fd= open("nolock.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
if(-1 == fd) {
perror("open");
return -1;
}
// 打印文件描述符
printf("fd = %d\n", fd);
// 逐个字符地写入,每写入一个休息一秒,方便我们作测试;
size_t i, len= strlen(argv[1]);
for(i=0; i< len; ++i) {
if(-1 == write(fd, &argv[1][i], sizeof(argv[1][i]))) {
perror("write");
return -1;
}
sleep(1);
}
getchar();
close(1);
return 0;
usage:
fprintf(stderr, "%s<string>\n", argv[0]);
return -1;
}
测试:打开一个进程,同时在另一个端口,再打开这个进程,例如两个端口命令如下:
$ a.out hello
$ a.out word
那么输入 nolock.txt 的不是 hello word,而是两个单词打乱顺序后的组合;
注意:首先需要明白,此程序运行后,各自的文件描述符都是 3,但是这两个 3 不是同一个文件描述符,
只是他们对应的磁盘文件是同一个(可以理解为文件描述符是进程级的,每个程序间互不影响);
本程序 open 是以追加方式打开,一个进程切换到另一个进程的时候,都是从文件尾开始写入,所以出现追加效果;
如果 open 后面不设置 O_APPEND 效果,那么会出现覆盖效果。
这里是两个独立进程,打开两个 open,也就打开了两个文件表;
如果是父子进程,则只打开一个文件表。
范例:lock1.c
#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 加读锁(起始位置为 0)
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 (); // 用于等待,等待我们开另一个进程;
// 解锁 MN
ulock (fd, 12, 2);
printf ("按<回车>,解锁EFGH...");
getchar ();
// 解锁 EFGH
ulock (fd, 4, 4);
close (fd);
return 0;
}
范例:lock2.c
#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) {
case SEEK_SET:
printf ("在距文件头");
break;
case SEEK_CUR:
printf ("在距当前位置");
break;
case SEEK_END:
printf ("在距文件尾");
break;
}
printf ("%ld字节处,为%ld字节加了", 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) 文件锁仅在不同进程间起作用,位于 V 节点表里(内存里)。
6) 通过锁同步多个进程对同一个文件的读写访问。
范例: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) { // 返回 0,则无法加锁
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;
}
测试结果如下:
# wlock 达内科技 | # wlock 有限公司
wlock.txt
<乱码>
# wlock 达内科技 -l | # wlock 有限公司 -l
wlock.txt
达内科技有限公司
范例: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 达内科技有限公司 | # rlock
<乱码>
# wlock 达内科技有限公司 -l | # rlock -l
达内科技有限公司
4. 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) 为以下值的位或:(6 位八进制,等于 18 位二进制)
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 的说明
A. 具有 S_ISUID/S_ISGID 位的可执行文件,其有效用户ID/有效组 ID,
并不取自由其父进程 (比如登录 shell) 所决定的,实际用户ID/实际组ID,
而是取自该可执行文件的属主ID/属组ID;如:/usr/bin/passwd
B. 具有 S_ISUID 位的目录,其中的文件或目录除 root 外,只有其属主可以删除。
C. 具有 S_ISGID 位的目录,在该目录下所创建的文件,继承该目录的属组 ID。
D.具有 S_ISVTX 位的可执行文件,在其首次执行并结束后,
其代码区将被连续地保存在磁盘交换区中,而一般磁盘文件中的数据块是离散存放的。
因此,下次执行该程序可以获得较快的载入速度。
现代 Unix 系统大都采用快速文件系统,已不再需要这种技术。
E.具有 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(实现类似于 ls -l 功能)
#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, "-"); // 普通文件
// 属主权限 U
strcat (s, m & S_IRUSR ? "r" : "-"); // 可读
strcat (s, m & S_IWUSR ? "w" : "-"); // 可写
strcat (s, m & S_IXUSR ? "x" : "-"); // 可执行
// 属组权限 G
strcat (s, m & S_IRGRP ? "r" : "-");
strcat (s, m & S_IWGRP ? "w" : "-");
strcat (s, m & S_IXGRP ? "x" : "-");
// 其他用户权限 O
strcat (s, m & S_IROTH ? "r" : "-");
strcat (s, m & S_IWOTH ? "w" : "-");
strcat (s, m & S_IXOTH ? "x" : "-");
// S
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, // 年从 1900 开始,月从 0 开始,其他正常开始;
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 (" 模式:%s\n", mtos (st.st_mode));
// printf (" 模式:%#o\n", 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;
}
5. access
#include <unistd.h>
int access (
const char* pathname, // 文件路径
int mode // 访问模式
);
1)成功返回 0,失败返回 -1。
2)按实际用户 ID 和实际组 ID(而非有效用户 ID 和有效组 ID),进行访问模式测试。
3)mode 取 R_OK/W_OK/X_OK 的位或,测试执行调用进程的用户对该文件,
是否可读/可写/可执行,或者取 F_OK,测试该文件是否存在。
范例:access.c
#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;
}
6. 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;
}
执行后,umask.txt 的执行权限变为:-r--r--r--
可以通过 umask(old); 恢复默认权限;
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
2. 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 - 获取文件描述符标志。
目前仅定义了一个文件描述符标志位 FD_CLOEXEC;
0 - 在通过 execve() 函数所创建的进程中,该文件描述符依然保持打开。
1 - 在通过 execve() 函数所创建的进程中,该文件描述符将被关闭。
F_SETFD - 设置文件描述符标志。
arg 只能取 FD_CLOEXEC (调用 exec 族函数后关闭该 fd)。
F_GETFL - 获取文件模式;不能获取 O_CREAT、O_EXCL 和 O_TRUNC。
F_SETFL - 追加文件模式;arg 只能取 O_APPEND 和 O_NONBLOCK。
范例:dup.c
#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);
/* fd1 复制给 fd2,若 fd2 已经打开,则关闭,返回 fd2
int fd3 = dup2 (fd1, fd2); // fd1, fd2, fd3 分别为:3,4,4(对应同一个文件表)
if (fd3 == -1) {
perror ("dup2");
return -1;
}
*/
// fd1 复制给 fd2,由于 fd2 已经打开,所以选择一个新的:
int fd3 = fcntl (fd1, F_DUPFD, fd2); // fd1, fd2, fd3 分别为:3,4,5(3 和 5 对应同一个文件表)
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;
}
分析:
int fd3 = dup2 (fd1, fd2); fd2 复制给 fd1,如果 fd2 已经打开,则先关闭 fd2,
然后再进行复制,那么 fd2 和 fd3 的文件描述符一样;且 fd1 fd2 fd3 三个文件的内容一样;
上例中,fd1 fd2 fd3 内容都为:123456789
int fd3 = fcntl (fd1, F_DUPFD, fd2); 同样把 fd2 复制给 fd1,如果发现 fd2 打开,
则另外再找一个最小文件描述符给 fd3,所以 fd1 fd2 fd3 文件描述符分别为:3 4 5;
同时,fd1 和 fd3 的内容一样,为:123789,fd2 内容为:456;
范例:flags.c
#include <stdio.h>
#include <fcntl.h>
void pflags (int flags) {
printf ("文件模式(%08X):", flags); // %08x 十六进制
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); // 输出类似格式:O_WRONLY | O_ASYNC
first = 0;
}
printf ("\n");
}
int main (void) {
int fd = open ("flags.txt", O_WRONLY | O_CREAT | 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 不能被 F_GETFL 获取
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 可以被 F_SETFL 追加
close (fd);
return 0;
}
3. 文件锁
#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。
1) 既可以锁定整个文件,也可以锁定特定区域。
2) 读锁 (共享锁)、写锁 (独占锁/排它锁)、解锁。
图示:rwlock.bmp、flock.bmp
3) 文件描述符被关闭 (进程结束) 时,自动解锁。
4) 劝谏锁 (协议锁)、强制锁。
举例:两个进程对同一个文件操作 nolock.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char* argv[]) {
// 从命令行输入要打印的字符串
if(argc < 2)
goto usage;
// 字符串写入这个文件中
int fd= open("nolock.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
if(-1 == fd) {
perror("open");
return -1;
}
// 打印文件描述符
printf("fd = %d\n", fd);
// 逐个字符地写入,每写入一个休息一秒,方便我们作测试;
size_t i, len= strlen(argv[1]);
for(i=0; i< len; ++i) {
if(-1 == write(fd, &argv[1][i], sizeof(argv[1][i]))) {
perror("write");
return -1;
}
sleep(1);
}
getchar();
close(1);
return 0;
usage:
fprintf(stderr, "%s<string>\n", argv[0]);
return -1;
}
测试:打开一个进程,同时在另一个端口,再打开这个进程,例如两个端口命令如下:
$ a.out hello
$ a.out word
那么输入 nolock.txt 的不是 hello word,而是两个单词打乱顺序后的组合;
注意:首先需要明白,此程序运行后,各自的文件描述符都是 3,但是这两个 3 不是同一个文件描述符,
只是他们对应的磁盘文件是同一个(可以理解为文件描述符是进程级的,每个程序间互不影响);
本程序 open 是以追加方式打开,一个进程切换到另一个进程的时候,都是从文件尾开始写入,所以出现追加效果;
如果 open 后面不设置 O_APPEND 效果,那么会出现覆盖效果。
这里是两个独立进程,打开两个 open,也就打开了两个文件表;
如果是父子进程,则只打开一个文件表。
范例:lock1.c
#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 加读锁(起始位置为 0)
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 (); // 用于等待,等待我们开另一个进程;
// 解锁 MN
ulock (fd, 12, 2);
printf ("按<回车>,解锁EFGH...");
getchar ();
// 解锁 EFGH
ulock (fd, 4, 4);
close (fd);
return 0;
}
范例:lock2.c
#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) {
case SEEK_SET:
printf ("在距文件头");
break;
case SEEK_CUR:
printf ("在距当前位置");
break;
case SEEK_END:
printf ("在距文件尾");
break;
}
printf ("%ld字节处,为%ld字节加了", 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) 文件锁仅在不同进程间起作用,位于 V 节点表里(内存里)。
6) 通过锁同步多个进程对同一个文件的读写访问。
范例: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) { // 返回 0,则无法加锁
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;
}
测试结果如下:
# wlock 达内科技 | # wlock 有限公司
wlock.txt
<乱码>
# wlock 达内科技 -l | # wlock 有限公司 -l
wlock.txt
达内科技有限公司
范例: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 达内科技有限公司 | # rlock
<乱码>
# wlock 达内科技有限公司 -l | # rlock -l
达内科技有限公司
4. 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) 为以下值的位或:(6 位八进制,等于 18 位二进制)
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 的说明
A. 具有 S_ISUID/S_ISGID 位的可执行文件,其有效用户ID/有效组 ID,
并不取自由其父进程 (比如登录 shell) 所决定的,实际用户ID/实际组ID,
而是取自该可执行文件的属主ID/属组ID;如:/usr/bin/passwd
B. 具有 S_ISUID 位的目录,其中的文件或目录除 root 外,只有其属主可以删除。
C. 具有 S_ISGID 位的目录,在该目录下所创建的文件,继承该目录的属组 ID。
D.具有 S_ISVTX 位的可执行文件,在其首次执行并结束后,
其代码区将被连续地保存在磁盘交换区中,而一般磁盘文件中的数据块是离散存放的。
因此,下次执行该程序可以获得较快的载入速度。
现代 Unix 系统大都采用快速文件系统,已不再需要这种技术。
E.具有 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(实现类似于 ls -l 功能)
#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, "-"); // 普通文件
// 属主权限 U
strcat (s, m & S_IRUSR ? "r" : "-"); // 可读
strcat (s, m & S_IWUSR ? "w" : "-"); // 可写
strcat (s, m & S_IXUSR ? "x" : "-"); // 可执行
// 属组权限 G
strcat (s, m & S_IRGRP ? "r" : "-");
strcat (s, m & S_IWGRP ? "w" : "-");
strcat (s, m & S_IXGRP ? "x" : "-");
// 其他用户权限 O
strcat (s, m & S_IROTH ? "r" : "-");
strcat (s, m & S_IWOTH ? "w" : "-");
strcat (s, m & S_IXOTH ? "x" : "-");
// S
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, // 年从 1900 开始,月从 0 开始,其他正常开始;
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 (" 模式:%s\n", mtos (st.st_mode));
// printf (" 模式:%#o\n", 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;
}
5. access
#include <unistd.h>
int access (
const char* pathname, // 文件路径
int mode // 访问模式
);
1)成功返回 0,失败返回 -1。
2)按实际用户 ID 和实际组 ID(而非有效用户 ID 和有效组 ID),进行访问模式测试。
3)mode 取 R_OK/W_OK/X_OK 的位或,测试执行调用进程的用户对该文件,
是否可读/可写/可执行,或者取 F_OK,测试该文件是否存在。
范例:access.c
#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;
}
6. 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;
}
执行后,umask.txt 的执行权限变为:-r--r--r--
可以通过 umask(old); 恢复默认权限;
相关文章推荐
- uc笔记06---chmod/fchmod,chown/fchown/lchown
- Matlab学习第二天 利用插值
- UGUI 代码 动态添加 Event Trigger 的事件
- 火云开发课堂 - 《Shader从入门到精通》系列 第十三节:在Shader中实现铅笔描边滤镜
- 张亦Excel VBA——基础篇
- 解析TCP/UDP协议的通讯软件
- spring 级联属性
- 自定义控件(视图)28期笔记09:自定义视图之继承自ViewGroup(仿ViewPager效果案例)
- web技术之负载均衡与缓存技术
- Nosql数据库——redis(五)主从复制
- java 时间字符串和时间戳(长整形long)之间的互转
- java基本类型和封装类型区别及应用
- 日期与字符串之间转换工具
- 随笔(2015.09)
- 实习小结十五:关于post文章页面的小结(倒数第二篇)
- uc笔记04---文件系统,文件常用命令(软/硬链接),文件描述符
- sendtoTarget()与sendtoMessage()
- java基本类型和封装类型区别及应用
- 网页导出excel
- [iOS]Core Data