linux系统编程笔记(二)——系统IO

 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;
}

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值