b站Linux系统编程(李慧琴)
文件描述符实现原理
标准IO的实现时依赖于系统IO来实现。FILE实际上是一个结构体类型,里面包含了文件描述符等信息。文件描述符fd实际上是一个int类型数字,代表文件数组下标。
使用open函数打开文件时,会创建一个结构体空间来控制文件,打开同一个文件多次,也会创建结构体多次。数组里面会存储结构体的首地址。我们只能看到文件描述符(数组下标),这样达到了封装效果。我们只需要通过文件描述符就可以找到控制该文件的结构体。
文件描述符优先使用当前数组内可用的最小数,例如,0,1,2,4,5被占用了,下一个会存储到3,而不会存储到6.
文件数组是存在于进程空间的,每个进程空间都会有文件数组。
使用close函数,假如没有指针指向控制文件的结构体就会被销毁。例如,3,4都指向同一个结构体,关闭3,还有4,所以结构体不会被销毁。关闭4,则结构体被销毁。结构体内部会记录有多少个指针指向它,当close时,会减减,如果为0,就会被销毁。
open、close 函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
#include <unistd.h>
int close(int fd);
open函数有两个,使用多参数实现的。当flags中有O_CREAT时,需要指定mode,如mode=0600。成功时返回文件描述符,失败返回-1.
flags常用到的有O_APPEND、O_CREAT、O_TRUNC、O_RDONLY、O_WRONLY、 O_RDWR。flags必须要有O_RDONLY、O_WRONLY、 O_RDWR中的其中一个,其它的都是可选的。
O_RDONLY只读,O_WRONLY只读,O_RDWR读写,O_APPEND追加(偏移量指向文件末尾),O_CREAT创建文件,O_TRUNC截取文件到0.
//以只读形式打开,如果文件不存在就创建
int fd=open(argv[1],O_RDONLY|O_CREAT,0600);
//以只写的形式打开,并把文件截取到0,如果文件不存在就创建
nt fd=open(argv[1],O_WRONLY|O_TRUNC|O_CREAT,0600);
read、write、lseek函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
read函数中,buf是存储空间,count是要读的字符个数,成功时返回的是读到的字符的个数,0表示已经读到文件末尾,错误时返回-1.
write函数中,buf是要写的内容,count是要写的字符个数,返回成功写入的字符个数,0表示没有东西可写,错误时返回-1.
lseek函数中,offset是偏移量,whence可以为SEEK_SET、SEEK_CUR、SEEK_END与flseek相同,返回值是从文件开头到移动完后的偏移量,错误时返回-1.
系统IO实现mycopy
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define BUFSIZE 128
char buf[BUFSIZE];
int main(int argc,char **argv){
if(argc<3) {
printf("need more argv\n");
exit(1);
}
int fd1=open(argv[1],O_RDONLY);
if(fd1<0) {
perror("open()");
exit(1);
}
int fd2=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0600);
if(fd2<0){
perror("open()");
exit(1);
}
int r_len=0;//读到的个数
int w_len=0;//总共写的个数
int len=0;//write一次成功写的个数
while(1){
r_len=read(fd1,buf,BUFSIZ);
if(r_len<0){
perror("read()");
exit(1);
}
else if(r_len==0) break;
w_len=0;
while(w_len<r_len){
len=write(fd2,buf+w_len,r_len-w_len);
w_len+=len;
}
}
close(fd1);
close(fd2);
exit(0);
}
系统IO与标准IO的区别
标准IO的吞吐量大,系统IO的响应速度快。标准IO存在缓冲区,当缓冲区满了或fflush强制刷新时,才会写到磁盘上。而系统IO每次写都是真实的写到磁盘上。影响用户体验的是吞吐量。
标准IO和系统IO不要同时使用,因为标准IO有缓冲区,而系统IO是直接写到磁盘上,会导致写的顺序,内容位置出现差错。
例如下面例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
putchar('a');
write(1,"b",1);//写到终端文件上
putchar('a');
write(1,"b",1);
putchar('a');
write(1,"b",1);
putchar('a');
write(1,"b",1);
}
按照顺序的话是"abababab",但是真实输出结果是"bbbbaaaa"。
共享文件
问题:删除文件的某一行。
覆盖方式删除:这种方法涉及到将要删除的行用后面的行内容覆盖,然后截断文件,以删除目标行。这适用于文本文件,但不适用于二进制文件。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#define BUF_SIZE 1024
char line_buf[BUF_SIZE];
int main(int argc ,char **argv){
if(argc<3){
fprintf(stderr,"need more argv\n");
exit(1);
}
//只读打开
FILE* fr=fopen(argv[1],"r");
if(fr==NULL) {
perror("fopen:");exit(1);
}
//读写打开
FILE* fw=fopen(argv[1],"r+");
if(fw==NULL) {
perror("fopen:");exit(1);
}
//删除的目标行数
int target_line=atoi(argv[2]);
if(target_line<1) {
fprintf(stderr,"don't have %d cow\n",target_line);
exit(1);
}
int line=1;
//获取目标行数的偏移量
while(line<target_line){
char *s=fgets(line_buf,BUF_SIZE,fr);
//s为空表示读发生错误
if(s==NULL) {
perror("fgets()");
exit(1);
}
line++;
}
int line_pos=ftell(fr);
fgets(line_buf,BUF_SIZE,fr);
int next_line_pos=ftell(fr);
//获取要删除的字符个数
int by_to_dele=next_line_pos-line_pos;
//设置偏移量
fseek(fw,line_pos,SEEK_SET);
fseek(fr,next_line_pos,SEEK_SET);
int read_num;
//与数组删除数类似
while((read_num=fread(line_buf,1,by_to_dele,fr))){
int write_num=0;
int num=0;
while(write_num<read_num){ //有可能写的时候没有一次成功写完
num=fwrite(line_buf+write_num,1,read_num-write_num,fw);
write_num+=num;
}
}
fseek(fw,-by_to_dele,SEEK_END);
//截断
ftruncate(fileno(fw),ftell(fw));
fclose(fr);
fclose(fw);
return 0;
}
dup、dup2函数
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup函数是对旧的文件描述符复制,并放到文件数组序号最低的位置。比如,0,1,2,4,5有文件,dup(4)会把4位置上的指针复制到3上。成功就会返回新的文件描述符,失败就会返回-1。
dup2是对旧的文件描述符进行复制,并放到指定的位置上。如果指定的位置上有其它文件占用,dup2会把占用的文件关闭,并放到那里。如果旧的文件描述符是不可用的话,会引起错误,假设指定的位置上有文件,不会被关闭。如果新描述符和旧文件描述符一样,dup2什么都不做,并返回新的文件描述符。成功执行返回新的文件描述符,失败就会返回-1。
问题:把终端文件重定向
dup用法。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int fd=open("tmp",O_WRONLY|O_CREAT|O_TRUNC,0600);
close(1);//空闲出1号位置
dup(fd); //把fd复制到1号
puts("hallo");
close(fd);
return 0;
}
但是由于close(1)和dup(fd)两句语句之间不是原子操作,当close(1)时,可能会被其它的线程抢占,会有竞争的风险。
dup2用法
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int fd=open("tmp",O_WRONLY|O_CREAT|O_TRUNC,0600);
dup2(fd,1);
puts("hallo111");
close(fd);
open("/dev/tty",O_WRONLY);
return 0;
}