1.引言
本章讲解的函数有:open、read、write、lseek、close、dup、fcntl、sync、fsync、ioctl都是系统调用,都是不带缓冲的I/O
2.文件描述符
open、create返回文件描述符fd,其他函数将fd作为参数。
1 #include
2 #include
3
4 int main()
5 {
6 printf("STDIN_FILENO = %d\n", STDIN_FILENO);
7 printf("STDOUT_FILENO = %d\n", STDOUT_FILENO);
8 printf("STDERR_FILENO = %d\n", STDERR_FILENO);
9 long openmax;
10 openmax = sysconf(_SC_OPEN_MAX);
11 printf("_SC_OPEN_MAX = %ld\n", openmax);
12 return 0;
13 }
结果:
分析:
1.#include
//for STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO、sysconf()
2.文件描述符:STDIN_FILENO = 0,与标准输入关联;STDOUT_FILENO = 1,与标准输出关联;STDERR_FILENO = 2,与标准错误关联;
3.long sysconf(int name);
OPEN_MAX = sysconf(_SC_OPEN_MAX);
文件描述符范围:0~OPEN_MAX-1
3.open和openat
#include
int open(const char *path, int flag, .../*mode_t mode*/);
int openat(int fd, const char *path, int flag, .../*mode_t mode*/);
返回值:成功,返回文件描述符;出错,返回-1
1 #include
2 #include
3 #include
4 #include
5
6 int main()
7 {
8 int fd1 = open("tempfile", O_RDONLY);
9 int fd2 = open("/home/zxin/ch3/tempfile", O_RDONLY);
10 int fd3 = openat(AT_FDCWD, "tempfile", O_RDONLY);
11 int fd4 = openat(fd3, "/home/zxin/ch3/tempfile", O_RDONLY);
12 //int fd5 = open("newfile", O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
13 int fd5 = open("newfile", O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
14 int fd6 = open("tempfile", O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
15
16 printf("fd1=%d\n", fd1);
17 printf("fd2=%d\n", fd2);
18 printf("fd3=%d\n", fd3);
19 printf("fd4=%d\n", fd4);
20 printf("fd5=%d\n", fd5);
21 printf("fd6=%d\n", fd6);
22 printf("NAME_MAX=%ld\n", pathconf("/", _PC_NAME_MAX));
23 return 0;
24 }
结果:
分析:
1.#include //for flag; #include //for mode_t mode
2.open中路径名可以是绝对路径或者相对于当前工作目录的相对路径
3.O_RDONLY=0;O_WRONLY=1;O_RDWR=2;
4.若文件已存在,则使用 O_CREAT | O_EXCL 会出错
5.使用open创建新文件的方法,open("newfile", O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); //同时使用 O_CREAT和O_EXCL确保创建的是新文件,而不会覆盖以前的文件
6.long sysconf(int name);
long pathconf(const char *pathname, int name);
long fpathconf(int fd, int name);
4.creat
#include
int creat(const char *path, mode_t mode);
返回值:成功,返回为只写打开的文件描述符;错误,返回-1
1 #include
2 #include
3
4 int main()
5 {
6 int fd1 = creat("cfile", S_IRWXU);
7 int fd2 = open("cfile2", O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);
8
9 printf("fd1=%d\n", fd1);
10 printf("fd2=%d\n", fd2);
11 return 0;
12 }
结果:
分析:
open("pathname", O_WRONLY|O_CREAT|O_TRUNC, mode)等效于creat("pathname", mode)
5.close
#include
int close(int fd);
返回值:成功,返回0;错误,返回-1
1 #include
2 #include
3 #include
4
5 int main()
6 {
7 int fd1 = open("tempfile", O_RDONLY);
8 int fd2 = 10;
9 int returnval, returnval2;
10 printf("returnval=%d\n", close(fd1));
11 printf("returnval2=%d\n", close(fd2));
12 return 0;
13 }
结果:
分析:
当一进程终止时,内核自动关闭该进程打开的所有文件。
6.lseek
#include
off_t lseek(int fd, off_t offset, int whence);
返回值:成功,返回新的文件偏移量;错误,返回-1
测试一个文件能否设置偏移量,程序如下:
1 #include
2 #include
3
4 int main()
5 {
6 if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
7 printf("cannot seek\n");
8 else
9 printf("seek ok\n");
10
11 return 0;
12 }
结果:
创建一有空洞的文件,程序如下:
1 #include
2 #include
3 #include
4 #include
5
6 int main()
7 {
8 char buf[]="abcdefghij";
9 char buf2[]="ABCDEFGHIJ";
10
11 int fd = creat("file.hole", S_IRWXU);
12 int n = write(fd, buf, 10);
13 if (n != 10)
14 {
15 perror("write() error");
16 exit(-1);
17 }
18
19 if (lseek(fd, 16384, SEEK_SET) == -1)
20 {
21 perror("lseek() error");
22 exit(-1);
23 }
24
25 if (write(fd, buf2, 10) != 10)
26 {
27 perror("write() error");
28 exit(-1);
29 }
30
31 return 0;
32 }
结果:
7.read和write
#include
ssize_t read(int fd, void *buf, size_t nbytes);
返回值:读到的字节数,若已到文件尾,返回0;出错,返回-1
ssize_t write(int fd, const void *buf, size_t nbytes);
返回值:成功,返回已写的字节数;出错,返回-1
使用read和write复制一个文件,程序如下:
1 #include
2 #include
3
4 #define BUFSIZE 4096
5
6 int main()
7 {
8 char buf[BUFSIZE];
9 int n;
10 while ((n = read(STDIN_FILENO, buf, BUFSIZE)) > 0)
11 {
12 if (write(STDOUT_FILENO, buf, n) != n)
13 {
14 perror("write() error");
15 exit(-1);
16 }
17 }
18 if (n < 0)
19 {
20 perror("read() error");
21 exit(-1);
22 }
23 return 0;
24 }
结果:
分析:
对于read,有多种情况会导致读到的字节数少于要求读的字节数(见书);但对于write来说,其返回值通常与要求写的字节数相等,否则表示出错;因此,read和write是否成功调用的判断条件也不相同。
8.文件共享
如果两个进程各自独立的打开了同一个文件,则有下图的关系:
9.原子操作
定义:原子操作指的是多步组成的一个操作。执行一个原子操作时,该原子操作里面的多步操作要么执行完所有步骤,要么一个步骤也不执行。内核不可能只执行原子操作中的某些步骤后又切换到其他进程去执行了。要么一个也不执行,要么全部执行完。
举例:lseek函数和O_APPEND标志
两个进程A,B要读同一个文件的偏移量,然后写该文件,假设当前偏移量为100。
用lseek:A用lseek后得到offset为100-->>进程切换到B-->>B用lseek后也得到offset为100-->>进程又切换到A-->>A写数据10个字节(该文件的100到110个字节被A写上数据)-->进程切换到B-->>B写数据10个字节(此时B不知道A已经写到110了,所以B还是从100开始写,导致A写的数据被B覆盖)
用O_APPEND标志:A读取文件偏移量然后写数据(原子操作)-->>B读取文件偏移量然后写数据(原子操作)
可见用O_APPEND后,读取偏移量和写数据这两步合成一个原子操作了,不会发生lseek中途进程切换的现象。
#include
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
返回值:读到的字节数,若已到文件尾,返回0;出错,返回-1
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
返回值:成功,返回已写的字节数;出错,返回-1
这两个函数都是原子性的定位并执行I/O
10.dup和dup2
int dup(int fd);
int dup2(int fd, int fd2);
返回值:成功,返回新的文件描述符;出错,返回-1
复制打开的文件描述符和标准输入描述符,程序如下:
1 #include
2 #include
3 #include
4
5 int main()
6 {
7 char buf[] = "apue";
8 int fd = open("tempfile", O_RDWR | O_TRUNC);
9 int newfd = dup(fd);
10 int newfd2 = dup2(STDIN_FILENO, 10);
11
12 printf("fd=%d, newfd=%d, newfd2=%d\n", fd, newfd, newfd2);
13 return 0;
14 }
结果:
分析:
返回的文件描述符与参数fd共享同一个文件表项,如下图所示:
11.sync、fsync、fdatasync
#include
int fsync(int fd);
int fdatasync(int fd);
返回值:成功,返回0;出错,返回-1
void sync(void);
功能:将内核中高速缓存写入磁盘。
sync:将修改过的块缓冲区排入写队列就返回,不等待写完成。
fsync、fdatasync:等待往磁盘上写完成;针对特定文件描述符;fdatasync只影响文件的数据部分。
12.fcntl
#
include
int fcntl(int fd, int cmd, .../*arg*/);
返回值:成功,依赖于cmd;错误,返回-1
功能:改变已打开文件的属性
测试并设置文件的状态标识,程序如下:
1 #include
2 #include
3 #include
4
5 void print_val(int val)
6 {
7
8 switch (val & O_ACCMODE)
9 {
10 case O_RDONLY:
11 printf("read only\n");
12 break;
13 case O_WRONLY:
14 printf("write only\n");
15 break;
16 case O_RDWR:
17 printf("read and write\n");
18 break;
19 }
20 if (val & O_APPEND)
21 printf("append\n");
22 if (val & O_NONBLOCK)
23 printf("nonblock\n");
24 }
25
26 int main(int argc, char *argv[])
27 {
28 if (argc != 2)
29 {
30 printf("argument error");
31 exit(-1);
32 }
33 int val = fcntl(atoi(argv[1]), F_GETFL, 0);
34 print_val(val);
35
36 fcntl(atoi(argv[1]), F_SETFL, O_NONBLOCK);
37 val = fcntl(atoi(argv[1]), F_GETFL, 0);
38 print_val(val);
39
40 return 0;
41 }
结果:
分析:
测试O_RDONLY、O_WRONLY、O_RDWR要用屏蔽字O_ACCMODE取得访问方式位。
13.ioctl
终端I/O是使用ioctl最多的地方,暂不知道该怎么用,日后补充
14./dev/fd
目录项是名为0,1,2,255的文件,打开文件/dev/fd/n等效于复制描述符n。
linux中的/dev/fd,它把文件描述符映射成指向底层物理文件的符号链接。
在shell中,打开/dev/fd,如下图所示: