• 第九周学习报告


    10.1 UnixI/O

    Unix文件是一个m字节的序列:

    B0, B1,…,BK,…,Bm-1
    

    引出UnixI/O(简单低级的应用接口):使输入输出以一种方式来执行`

    • 打开文件
    • 改变当前文件位置
    • 读写文件
    • 关闭文件

    Unix外壳创建每个进程开始时都有三个打开文件:(头文件unistd.h定义常量)

    标准输入(描述符为0)STDIN_FILENO
    标准输出(描述符为1)STDOUT_FILENO
    标准错误(描述符为2)STDERR_FILENO
    

    执行seek

    显示文件当前位置k(从文件头起始的字节偏移量):

    触发end-of-file(EOF)

    读写文件时,给定一个大小为m字节文件,k>=m时

    *结尾并没有明确"EOF符号"

    10.2 打开和关闭文件

    打开文件

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    
    int open(char *filename, int flags,mode_t mode)
    

    open函数

    将filename转换为一个文件描述符,并返回描述符数字

    • flags参数 : 指明进程打算如何访问这个文件

        O_RDONLY 只读
        O_WRONLY 只写
        O_RDWR 可读可写
      

      *也可以以多个掩码的或,为写提供一些额外提示

        O_CREAT 如果文件不存在,就创建一个截断的(空)文件
        O_TRUNC 如果文件存在,就截断它
        o_APPEND 在每次写操作前,设置文件位置到文件的结尾处
      
    • mode参数 : 指定新文件的访问权限

      每个进程都有一个umask,通过调用umask来设置。
      当进程通过带某个mode参数的open函数用来创建一个新文件时,文件的访问权限被设置为mode & ~umask。

    举几个栗子:

    1. 以读方式打开已有文件:

      fd=Open("foo.txt",O_RDONLY,0);
      
    2. 打开一个已存在的文件,并在后面添加一些数据

       fd=Open("foo.txt",O_WRONLY|O_APPEND,0)
      
    3. 创建一个新文件,文件拥有者拥有读写权限,其他用户读权限

       给mode和umask默认值:
       #define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH|S_IWOTH
       #define DEF_UMASK S_IWGRP|S_IWOTH
       
       umask(DEF_UMASK);
       fd=Open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,DEF_MODE);
      

    关闭文件

    #include <unistd.h>
    int close<int fd>;(返回:成功为0,出错为-1)
    

    10.3读和写文件

    #include<unistd.h>
    ssize_t read(int fd,void *buf,size_t n);
    										/*返回:若成功则为读的字数,0为EOF,-1为出错*/
    ssize_ write(int fd,const void *buf,size_t n);
    										/*返回:若成功则为写的字节数,若出错则为-1*/
    

    read函数

    从描述符fd的当前文件位置拷贝n个字节到存储位置buf。

    write函数

    从存储器位置buf拷贝至多个n字节到描述符fd的当前文件位置

    lseek函数

    通过调用lseek函数,应用程序能够显式地修改当前文件的位置。

    不足值

    read和write传送的字节比应用程序要求的要少。

    出现情况如下:

    • 读时遇到EOF
    • 从终端读文本行 (返回不足值为文本行大小)
    • 读和写网络套字(socket)
      1. 内存缓冲约束和较长的网络延迟
      2. Unix管道

    10.4用RIO包健壮地读写

    RIO提供两类不同的函数:

    无缓冲的输入输出函数

    #include "csapp.h"
    
    ssize_t rio_readn(int fd, void *usrbuf, size_t n);
    ssize_t rio_writen(int fd,void *usrbuf, size_t n);
    			/*返回:若成功则为传送的字节数,若EOF则为0(只对rio_readn而言),若出错则为-1*/
    

    rio_readn函数

    从描述符fd的当前位置最多传送n个字节到存储器位置usrbuf,在遇到EOF时只能返回一个不足值

    rio_writen函数

    从位置usrbuf传送n个字节到描述符fd。决不会返回不足值。

    带缓冲的输入函数

    文本行就是一个由换行符结尾的ASCII码符序列。

    #include "csapph.h"
    void rio_readinitb(rio_t *rp,int fd)
    
    ssize_t rio_readlined(rio_t,void *usrbuf,size_t maxlen)
    ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_t n)
    			                         /*返回:若成功则为读的字节数,若为EOF则为0,若出错则为-1*/
    

    包装函数(rio_readlined)

    从一个内部读缓冲区拷贝一个文本行,当缓冲区变为空时,会自动地调用read重新填满缓冲区。
    函数最多读maxlen-1个字符,余下的一个字符留给结尾的空字符。
    对于包含文本行也包含二进制数据的文件,提供rio_readn,和rio_readlineb一样的缓冲区中传送原始字节。
    对于带缓冲的函数的调用,不应和rio_readn函数交叉使用。

    rio_read函数

    RIO读程序的核心是rio_read函数。
    出错返回-1,并适当地设置errno。若为EOF时返回0,。如果要求字节数超过了读缓冲区内未读字节的数量,它会返回一个不足值。

    10.5 读取文件元数据

    应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(元数据)

    #include <unistd.h>
    #include <sys/stat.h>
    
    int stat(const char *filename, struct stat *buf);
    int fstat(int fd,struct stat *buf);
    								/*返回:成功返回0,若出错则为-1*/
    

    stat以文件名作为输入,fstat以文件描述符作为输入

    普通文件

    某种类型的二进制或文本数据(对内核而言两者并没有什么区别)

    目录文件

    包含关于其他文件的信息

    套接字

    一种用来通过网络和其他进程通信的文件

    st_size

    包含了文件的字节数大小。

    st_mode

    编码了文件访问许可位和文件类型。
    Unix提供的宏指令根据st_mode成员来确定文件的类型。

    10.6 共享文件

    内核用三个相关的数据结构来表示打开的文件

    描述符表
    文件表
    v-node表

    文件表包括

    当前文件位置
    引用计数
    指向v-node表中对应表项的指针

    关闭一个描述符会减少相应的文件表表项的引用计数。

    *fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

    父子进程共享文件时,在内核删除相应文件表表项之前,父子进程必须都关闭了它们的描述符。

    10.7 I/O重定向

    #inlcude <unistd.h>
     
    int dup(int oldfd,int newfd)
    				/*返回:若成功则为非负的描述符,若出错则为-1*/
    

    dup2函数

    拷贝描述符表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。

    10.8 标准I/O

    标准I/O库

    打开和关闭文件的函数fopen fclose
    读和写字节的函数fgets fputs
    复杂格式化的I/O函数scanf printf
    

    一个流就是一个指向FILE类型结构的指针

    每个ANSI C程序开始时都有三个打开流:

    stdio	标准输出
    stdout  标准输入
    stderr  标准错误
    

    FILE类型流是对文件描述符流缓冲区的抽象

    流缓冲区的目的:
    使开销较高的Unix I/O系统调用的数量尽可能小。

    10.9 综合:我该使用那些I/O函数

    标准I/O流,全双工,so为了防止冲突:

    • 限制一:跟在输出函数后的输入函数。——采用在每个输入操作前刷新缓冲区
    • 限制二:跟在输入函数后的输出函数。——对同一个打开的套字描述符打开两个流,一读一写。

    大多数情况使用标准I/O,但在网络套字上不要使用,这时使用健壮地RIO函数。

    格式化输出

    使用sprintf函数在存储器中格式化一个字符串,然后用rio_writen把它发送到套接口。

    格式化输入

    使用rio_readlineb来读一个完整的文本行,然后使用sscanf从文本行提取不同的字段。

    错误处理

    • Unix风格的错误处理

      早期开发的函数返回值既包括错误代码也包括有用的结果

    • POSIX风格的错误

      只用返回值来表示成功(0)或失败(!0),任何有用的结果都返回在通过引用传递进来的函数参数中

    • DNS风格的错误处理

      在失败是返回NULL指针,并设置全局变量h_errno

    • 小结
      包容不同错误处理风的错误报告函数

    include "csapp.h"

    void unix_error(char msg);

    void posix_error(int code,char msg);

    void dns_error(char msg);

    void app_error(char msg);

    做完练习之后,记住几点知识点:

    Unix生命周期开始后,就自动有了三个描述符了stdio stdout stderr 0 1 2

    open函数总是打开返回最低的未打开的描述符。

    练习10.3中有调用fork

    关于fork
    fork函数详解在这里,注意红字部分:子父进程,一次调用,两次返回。
    http://blog.csdn.net/jason314/article/details/5640969

    这一章我看完的时候还觉得蛮轻松的,做练习的时候就不轻松了,答案总是和自己想的不一样,挺崩溃的,好好分析完练习感觉才真正知道描述符是如何工作的,心情大爽

    实践

    ls1:

    ls 命令将每个由 Directory 参数指定的目录或者每个由 File 参数指定的名称写到标准输出,以及您所要求的和标志一起的其它信息。

    > #include	<stdio.h>
    

    include <sys/types.h>//基本系统数据类型

    include <dirent.h>//包含了许多UNIX系统服务的函数原型

         void do_ls(char []);
    

    int main(int argc, char *argv[])//主函数参数在编写交互式程序时有用,可以用来接受你输入的命令
    argc是你输入字符个数吧,后面是保存交互式用户输入的一个数组.程序可以识别这两个参数识别用户输入,进一步做出相应的反应.
    {
    if ( argc == 1 )//
    do_ls( "." );
    else
    while ( --argc ){
    printf("%s: ", *++argv );
    do_ls( *argv );
    }

    return 0;
    }

    void do_ls( char dirname[] )
    {
    DIR *dir_ptr;
    struct dirent *direntp;

    if ( ( dir_ptr = opendir( dirname ) ) == NULL )//打开失败返回空指针时,打印该目录的名字
    fprintf(stderr,"ls1: cannot open %s ", dirname);
    else
    {
    while ( ( direntp = readdir( dir_ptr ) ) != NULL )//递归的打印成员
    printf("%s ", direntp->d_name );
    closedir(dir_ptr);
    }
    }

    ls2
    功能和ls1一样,并打印权限大小时间等等

    > #include	<stdio.h>
    

    include <string.h>

    include <sys/types.h>

    include <dirent.h>

    include <sys/stat.h>

    void do_ls(char[]);
    void dostat(char *);
    void show_file_info( char *, struct stat *);
    void mode_to_letters( int , char [] );
    char *uid_to_name( uid_t );
    char *gid_to_name( gid_t );

    int main(int argc, char *argv[])
    {
    if ( argc == 1 )
    do_ls( "." );
    else
    while ( --argc ){
    printf("%s: ", *++argv );
    do_ls( *argv );
    }

    return 0;
    }

    void do_ls( char dirname[] )
    {
    DIR *dir_ptr;
    struct dirent *direntp;

    if ( ( dir_ptr = opendir( dirname ) ) == NULL )
    fprintf(stderr,"ls1: cannot open %s ", dirname);
    else
    {
    while ( ( direntp = readdir( dir_ptr ) ) != NULL )
    dostat( direntp->d_name );
    closedir(dir_ptr);
    }
    }

    void dostat( char *filename )
    {
    struct stat info;

    if ( stat(filename, &info) == -1 )
    perror( filename );
    else
    show_file_info( filename, &info );
    }

    void show_file_info( char *filename, struct stat *info_p )
    {
    char *uid_to_name(), *ctime(), *gid_to_name(), *filemode();
    void mode_to_letters();
    char modestr[11];

    mode_to_letters( info_p->st_mode, modestr );

    printf( "%s" , modestr );
    printf( "%4d " , (int) info_p->st_nlink);
    printf( "%-8s " , uid_to_name(info_p->st_uid) );
    printf( "%-8s " , gid_to_name(info_p->st_gid) );
    printf( "%8ld " , (long)info_p->st_size);
    printf( "%.12s ", 4+ctime(&info_p->st_mtime));
    printf( "%s " , filename );

    }

    void mode_to_letters( int mode, char str[] )
    {
    strcpy( str, "----------" );

      if ( S_ISDIR(mode) )  str[0] = 'd';    
      if ( S_ISCHR(mode) )  str[0] = 'c';    
    if ( S_ISBLK(mode) )  str[0] = 'b';    
    
    if ( mode & S_IRUSR ) str[1] = 'r';    
    if ( mode & S_IWUSR ) str[2] = 'w';
    if ( mode & S_IXUSR ) str[3] = 'x';
    
    if ( mode & S_IRGRP ) str[4] = 'r';    
    if ( mode & S_IWGRP ) str[5] = 'w';
    if ( mode & S_IXGRP ) str[6] = 'x';
    
    if ( mode & S_IROTH ) str[7] = 'r';    
    if ( mode & S_IWOTH ) str[8] = 'w';
    if ( mode & S_IXOTH ) str[9] = 'x';
    

    }

    include <pwd.h>

    char *uid_to_name( uid_t uid )
    {
    struct passwd *getpwuid(), *pw_ptr;
    static char numstr[10];

    if ( ( pw_ptr = getpwuid( uid ) ) == NULL ){
    sprintf(numstr,"%d", uid);
    return numstr;
    }
    else
    return pw_ptr->pw_name ;
    }

    include <grp.h>

    char *gid_to_name( gid_t gid )
    {
    struct group *getgrgid(), *grp_ptr;
    static char numstr[10];

    if ( ( grp_ptr = getgrgid(gid) ) == NULL ){
    sprintf(numstr,"%d", gid);
    return numstr;
    }
    else
    return grp_ptr->gr_name;
    }

    who1

    打印了utbufp一些信息:name,line,time,host

    > #include	<stdio.h>
    

    include <stdlib.h>

    include <utmp.h>

    include <fcntl.h>

    include <unistd.h>

    define SHOWHOST

    int show_info( struct utmp *utbufp )
    {
    printf("%-8.8s", utbufp->ut_name);
    printf(" ");
    printf("%-8.8s", utbufp->ut_line);
    printf(" ");
    printf("%10ld", utbufp->ut_time);
    printf(" ");

    ifdef SHOWHOST

    printf("(%s)", utbufp->ut_host);

    endif

    printf(" ");

    return 0;
    }
    int main()
    {
    struct utmp current_record;
    int utmpfd;
    int reclen = sizeof(current_record);

    if ( (utmpfd = open(UTMP_FILE, O_RDONLY)) == -1 ){
    perror( UTMP_FILE );
    exit(1);
    }
    while ( read(utmpfd, &current_record, reclen) == reclen )
    show_info(&current_record);
    close(utmpfd);
    return 0;
    }


    cp1

    cp命令用来复制文件或者目录
    该程序的主要实现思想是:打开一个输入文件,创建一个输出文件,建立一个 BUFFERSIZE 大小的缓冲区;然后在判断输入文件未完的循环中,每次读入多少就向输出文件中写入多少,直到输入文件结束。

    #include        <stdio.h>
    #include        <stdlib.h>
    #include        <unistd.h>
    #include        <fcntl.h>
    
    #define BUFFERSIZE      4096//表示缓冲区的大小
    #define COPYMODE        0644//创建文件的权限
    
    void oops(char *, char *);
    
    int main(int argc, char *argv[])
    {
     int in_fd, out_fd, n_chars;//n_chars存放读出字节数的变量
     char buf[BUFFERSIZE];
     if (argc != 3) {//三个参数程序名 argv[0]、拷贝源文件 argv[1]、目标文件 argv[2]
      fprintf(stderr, "usage: %s source destination
    ", *argv);
      exit(1);
     }
    
     if ((in_fd = open(argv[1], O_RDONLY)) == -1)//用 open 系统调用以 O_RDONLY 只读模式打开拷贝源文件,如果打开失败就输出错误信息并退出
      oops("Cannot open ", argv[1]);
    
     if ((out_fd = creat(argv[2], COPYMODE)) == -1)//creat 系统调用以 COPYMODE 的权限建立一个文件,如果建立失败函数的返回值为 -1 的话,就输出错误信息并退出。
      oops("Cannot creat", argv[2]);
    
    /*拷贝的主要过程*/
     while ((n_chars = read(in_fd, buf, BUFFERSIZE)) 0)//没有异常情况和文件没有读到结尾
      if (write(out_fd, buf, n_chars) != n_chars)
       oops("Write error to ", argv[2]);
     if (n_chars == -1)
      oops("Read error from ", argv[1]);
    
    
     if (close(in_fd) == -1 || close(out_fd) == -1)//若出现
      oops("Error closing files", "");
    }
    
    void oops(char *s1, char *s2)//呀,对不起orz:处理错误信息
    {
     fprintf(stderr, "Error: %s ", s1);//错误信息
     perror(s2);//perror( ) 用来将上一个函数发生错误的原因输出到标准设备(stderr)
     exit(1);
    }
    

    cp实现的解析参考这里http://www.ibm.com/developerworks/cn/linux/l-cn-commands/www.ibm.com/developerworks/cn/linux/l-cn-commands/

  • 相关阅读:
    REVERSE!REVERSE!REVERSE!
    java和数据结构的面试考点
    39. recover rotated sorted array恢复旋转排序数组
    33. Search in Rotated Sorted Array旋转数组二分法查询
    搜索旋转排序数组 II
    Eclipse 查找
    在Java中返回多个值
    Java的标识符
    fwprintf (File input/output) – C 中文开发手册
    CSS盒子模型介绍 | CSS Box Model: Introduction to the CSS box model (Miscellaneous Level 1) – CSS 中文开发手册
  • 原文地址:https://www.cnblogs.com/midori/p/4947588.html
Copyright © 2020-2023  润新知