知识储备:
C库I/O:点击打开链接
Linux系统I/O:
文件描述符:
每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的信息,称为进程描述符(Process Descriptor),而在操作系统理论中称为进程控制块(PCB,Process Control Block)。task_struct中有一个指针指向files_struct结构体,称为文件描述符表,其中每个表项包含一个指向已打开的文件的指针,用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引(即0、1、2、3这些数字),这些索引就称为文件描述符(File Descriptor)。当调用open打开一个文件或创建一个新文件时,内核分配一个文件描述符并返回给用户程序,该文件描述符表项中的指针指向新打开的文件。当读写文件时,用户程序把文件描述符传给read或write,内核根据文件描述符找到相应的表项,再通过表项中的指针找到相应的文件。0,1,2系统自动打开,分别代表标准输入,标准输出,和标准错误。
系统最多能打开多少个文件描述符,受内存影响,可用: cat/proc/sys/fs/file_max 命令查看
1.int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:打开或者创建一个文件
返回值:成功返回文件描述符。出错返回-1
参数:
1.pathname参数是要打开或创建的文件名,和fopen一样,pathname既可以是相对路径也可以是绝对路径。
2.flags参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,所以这些常数的宏定义都以O_开头,表示or。
必选项:以下三个常数中必须指定一个,且仅允许指定一个。
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 可读可写打开
以下可选项可以同时指定0个或多个,和必选项按位或起来作为flags参数。可选项有很多,这里只介绍一部分,其它选项可参考open(2)的Man Page:
O_APPEND 表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该文件的访问权限。
O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
O_TRUNC 如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate)为0字节。
3.mode指定文件权限,可以用八进制数表示,比如0644,详见open(2)的Man Page。要注意的是,文件权限由open的mode参数和当前进程的umask掩码共同决定。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int op = open("hehe.txt", O_RDWR);
if(-1 == op){
perror("open");
exit(1);
}
while(1)
{
pause();
}
return 0;
}
2.int close(int fd);
功能:关闭一个已经打开的文件
返回值:成功返回0,出错返回-1并且设置errno
参数fd即是要关闭的文件描述符。
补充:由open返回的文件描述符一定是该进程尚未使用的最小描述符。由于程序启动时自动打开文件描述符0、1、2,因此第一次调用open打开文件通常会返回描述符3,再调用open就会返回4。可以利用这一点在标准输入、标准输出或标准错误输出上打开一个新文件,实现重定向的功能。例如,首先调用close关闭文件描述符1,然后调用open打开一个常规文件,则一定会返回文件描述符1,这时候标准输出就不再是终端,而是一个常规文件了,再调用printf就不会打印到屏幕上,而是写到这个文件中了。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
close(1);
int fd = open("closefile", O_RDWR|O_CREAT, 0644);
if(-1 == fd){
perror("open");
exit(1);
}
char *msg = "abcdefghijklmnopqrstuvwxyz";
write(fd, msg, strlen(msg));
//1被关闭,无法打印到屏幕,而是写入到文件当中
printf("fd = %d\n", fd);
printf("this is czf!");
close(fd);
return 0;
}
3. ssize_t read(int fd, void *buf, size_t count);
功能:从打开的设备或文件中读取数据
返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0
参数:参数count是请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移。count小于buf_size时,将fd内容整体复制。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h> // ./a.out aaa bbb 把aaa中的内容读到buf再写入到bbb
int main(int argc, char *argv[])
{
if(3 != argc)
{
fprintf(stderr,"usage:%s from to\n", argv[0]);//输入错误 -- 输入格式 ./a.out aaa bbb
exit(0);
}
int fd_in = open(argv[1],O_RDONLY);
if(-1 == fd_in){
perror("open");
exit(1);
}
int fd_out = open(argv[2], O_WRONLY|O_CREAT,0644);//只写方式打开
if(-1 == fd_out){
perror("open");
close(fd_in);
exit(1);
}
char buf[10];
while(1){
memset(buf, 0x00, sizeof(buf));
int r = read(fd_in, buf, 10);
if(r <= 0) break;
write(fd_out, buf, r);
write(1, buf, r);
}
close(fd_in);
close(fd_out);
return 0;
}
4. ssize_t write(int fd, const void *buf, size_t count);
功能:向打开的设备或文件中写数据
返回值:成功返回写入的字节数,出错返回-1并设置errno
参数:同read
5. off_t lseek(int fd, off_t offset, int whence);
功能:lseek和标准I/O库的fseek函数类似,可以移动当前读写位置
返回值:lseek成功时返回当前偏移量失败时返回-1
参数:同fseek offset可正可负 whence 可以设置SEEK_SET, SEEK_CUR, SEEK_END
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h> //从文件开始每隔一个字符读一个
int main()
{
int fd = open("lseekfile",O_RDWR|O_CREAT, 0644);
if(-1 == fd){
perror("open");
exit(1);
}
char *msg = "abcdefghjklimnopqrstuvwxyz";
write(fd, msg, strlen(msg));
lseek(fd, 0, SEEK_SET);
char c;
while(1){
if(read(fd, &c, 1) <= 0) break;
// write(1, &c, 1);
printf("%c", c);
lseek(fd, 1, SEEK_CUR);
}
close(fd);
return 0;
}
6.int truncate(int fd, off_t length);
功能:文件截断 (占位置)
返回值:成功返回0, 失败返回-1
参数:lenth占位长度
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h> //截断一个内存大小为1K的文件
int main()
{
int op = open("FileOfTruncate", O_RDWR | O_CREAT, 0644);
if(-1 == op){
perror("open");
exit(1);
}
ftruncate(op, 1024); printf("create ok!\n");
write(op, "aaaaaa", 2);
return 0;
}
7.int dup(int oldfd)文件描述符的复制,从最小找第一个没有被使用过的描述符来使用
int dup2(int oldfd, int newfd);
功能:复制一个现存的文件描述符
返回值:成功返回新的文件描述符,出错返回-1
#include <stdio.h> //close.c
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
close(1);
int fd = open("closefile", O_RDWR|O_CREAT, 0644);
if(-1 == fd){
perror("open");
exit(1);
}
char *msg = "abcdefghijklmnopqrstuvwxyz";
write(fd, msg, strlen(msg));
printf("fd = %d\n", fd);//1被关闭,无法打印到屏幕
printf("this is czf!");
close(fd);
return 0;
}
#include <stdio.h> //dup-close.c
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd = open("dupfile", O_RDWR|O_CREAT, 0644);
if(-1 == fd){
perror("open");
exit(1);
}
char *msg = "abcdefghijklmnopqrstuvwxyz";
write(fd, msg, strlen(msg));
fprintf(stderr, "fd = %d\n", fd);
close(1);
dup(fd); //文件描述复制
printf("after fd = %d\n ", fd); //1已经被关闭,
//本来应该输出到屏幕的,最终打印到文件当中
printf("this is czf!");
close(fd);
return 0;
}
文件共享:
父进程的文件描述符都被复制到子进程中。父子进程打开相同的文件表项。这样的共享方式,让父子进程对同一文件使用了一个文件偏移量。如果父子进程写到同一描述符文件,但又没有任何形式的同步(父进程等待子进程),那么它们的输出就会相互混合(假定所用的描述符时fork之前打开的)。父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读写的任一共享描述符的文件偏移量一致行了相应的更新。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd = open("forkfile",O_RDWR);
if(-1 == fd) perror("open"),exit(1);
printf("before fd = %d\n",fd);
pid_t pid = fork();
if(-1 == pid){
perror("fork");
exit(1);
}
else if (pid > 0){
write(fd, "ABCDEFGH", 8);//父进程写入
close(fd); //关闭(引用计数减一),如果子进程能读出来,
//说明用的是同一个文件表,文件表保存了(打开标志,当前读写位置,引用计数,和V节点指针等)
printf("parent fd = %d\n",fd);
printf("parent finish!\n");
}
else{
sleep(1);
char buf[3] = {};
// lseek(fd, 0, SEEK_SET);
if(read(fd, buf, 2) == -1) perror("read");
printf("buf = %s\n", buf);
printf("child fd = %d\n",fd);
printf("child finish\n");
// write(fd, "aaaaa",5);
close(fd);
}
return 0;
}