嵌入式 IO编程

前言:

1、IO编程:数据和文件之间相互通信的过程 。IO编程就是对文件的输入输出操作。 

2、IO编程分为标准IO,和文件IO。

简单介绍一下两者区别:

        标准IO:由ANSIC中定义的用于I/O操作的一系列函数,引用C语言库支持C库操作应用层,,是一种高级IO,主要的操作对象是普通文件。为减轻系统开销,标准I/O使用时候会在用户区创建缓冲区,读写时先操作缓冲区,在合适的时机再通过系统调用访问实际空间,从而减少使用系统的调用次数。

        文件I/O:文件IO是由POSIX(可以移植系统接口)定义的,很多系统都支持POSIX标准,如UNIX,Linux。文件IO通常针对特殊文件,比如设备文件。但是文件IO不支持C库。 

 

3、Linux中一切皆文件,其中文件可以分为一下七类:

  普通文件          -
    目录文件        d
    管道文件        p 
    字符设备        c
    块设备          b
    链接文件        l 
    套接字          s 

一、Linux标准I/O编程

1、标准IO核心----流

1.1首先说一下什么是流:

        标准IO核心对象就是流。当用标准IO打开一个文件时,就会自动创建一个FIFE结构体描述该文件,我们将该结构体称为流。标准I/O主要针对“流”进行操作。

struct _IO_FILE {
   int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
 #define _IO_file_flags _flags
 
   /* The following pointers correspond to the C++ streambuf protocol. */
   /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
   char* _IO_read_ptr;   /* Current read pointer */
   char* _IO_read_end;   /* End of get area. */
   char* _IO_read_base;  /* Start of putback+get area. */
   char* _IO_write_base; /* Start of put area. */
   char* _IO_write_ptr;  /* Current put pointer. */
   char* _IO_write_end;  /* End of put area. */
   char* _IO_buf_base;   /* Start of reserve area. */
   char* _IO_buf_end;    /* End of reserve area. */
   /* The following fields are used to support backing up and undo. */
   char *_IO_save_base; /* Pointer to start of non-current get area. */
   char *_IO_backup_base;  /* Pointer to first valid character of backup area */
   char *_IO_save_end; /* Pointer to end of non-current get area. */
 
   struct _IO_marker *_markers;
   struct _IO_FILE *_chain;
1.2、Linux开机自启动文件

    在Linux里面有三个特殊文件,会在每一次运行程序的时候自动打开
                                                 文件指针 
    标准输入(文件)                   stdin                键盘
    标准输出(文件)                   stdout              屏幕
    标准错误输出(文件)            stderr               屏幕

2、标准IO的缓冲

标准IO缓冲有三种类型:全缓冲,行缓冲,无缓冲。

        全缓冲:缓冲区满了,或者使用fflush刷新

        行换从:缓冲区满了,或者使用fflush刷新,或者遇到\n。

        无缓冲:向缓冲区中写数组,每写一个字符都会立即被刷新。

3、流的操作

        流的操作包括:打开,关闭,读写,定位等。

3.1文件打开:
       #include <stdio.h>
            //打开文件
            FILE *fopen(const char *pathname, const char *mode);
            参数:
                pathname:文件路径
                mode:打开方式
                   r    只读的方式打开文件,文件必须存在。
                   r+   读写的方式打开文件,文件必须存在。
                   w    只写的方式打开文件,文件不存在则创建,存在则清空
                   w+   读写的方式打开文件,文件不存在则创建,存在则清空  
                   a    追加只写方式打开文件,文件不存在则创建,存在则追加写入  
                   a+   追加读写方式打开文件,文件不存在则创建,存在则追加写入  
                   
            返回值:
                成功返回文件指针,失败返回 NULL ,并且设置错误号。
3.2文件的读写

3.2.1格式化读写:

格式化输出: 

#include <stdio.h>
            int printf(const char *format, ...);
            //向stream指向的文件中输出数据
            int fprintf(FILE *stream, const char *format, ...);
            //向内存中输出数据,因为没有指定输出数据的大小,容易造成内存泄漏,因此不安全
            int sprintf(char *str, const char *format, ...);
            //向内存中输出数据,指定了输出数据大小,因此类型安全   
            int snprintf(char *str, size_t size, const char *format, ...);
            参数:
                str:内存首地址
                size:内存的大小
                format:数据的格式
                变参:要输入的数据
格式化输入:

 #include <stdio.h>
            //从标准输入读取数据
            int scanf(const char *format, ...);
            //从steam指定的文件中输入读取数据
            int fscanf(FILE *stream, const char *format, ...);           
            //从指定的内存中输入数据
            int sscanf(const char *str, const char *format, ...);
3.2.2非格式化输入输出

非格式化的输入输出主要依赖于库函数的调用:

1>按字符读写:
            
                #include <stdio.h>               
                int fgetc(FILE *stream);//从steam指定的文件中读取一个字符,是一个函数   
                int getc(FILE *stream);//从steam指定的文件中读取一个字符,是一个宏  
                int getchar(void); //从标准输入读取一个字符    
                参数:
                    stream:文件指针
                返回值:
                    成功一个字符(int),出错或者到达文件末尾EOF。

                #include <stdio.h>       
                int fputc(int c, FILE *stream); //向stream指向的文件中输入一个字符,是一个函数             
                int putc(int c, FILE *stream); //向stream指向的文件中输入一个字符,是一个宏 
                int putchar(int c); //向标准输出输出一个字符 
                参数:
                    c:要输入的字符
                    stream:文件指针

2>按行读写:
                #include <stdio.h>
               //向stream指向文件中读取一行数据,保存到s指向的内存中
               //会将\n 一起读入到内存中
               //最多只能读size -1 个字符,在末尾主动添加 \0
                char *fgets(char *s, int size, FILE *stream);
                参数:
                    s:内存首地址
                    size:读取字符的个数
                    stream:文件指针
                返回值:
                    成功返回内存首地址,失败或者读到文件末尾返回NULL

                // 默认向键盘获得一行数据,保存到s指向的内存中,因为没有限制内存长度,
               // 容易造成内存泄漏
               char *gets(char *s);            
                #include <stdio.h>
                //向stream指向文件中写入一行数据
                int fputs(const char *s, FILE *stream);
                参数:
                    s:内存首地址
                    stream:文件指针  
                //默认向标准输出打印一行数据,保存到s指向的内存中,因为没有限制内存长度
                // 容易造成内存泄漏
                int puts(const char *s);  

3>按快读写:

                #include <stdio.h>
                //按块读取
                size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
                参数:
                    ptr:内存首地址
                    size:读取数据对象的大小
                    nmemeb:读取的个数
                    stream:文件指针 
                返回值:
                    实际读取到的个数,失败返回0
                                
                //按块写入
                size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
               参数:
                    ptr:内存首地址
                    size:写入数据对象的大小
                    nmemeb:写入的个数
                    stream:文件指针 
                返回值:
                    实际写入到的个数,失败返回0 






3.3文件流的定位

 文件指针的偏移和定位
    文件指针的偏移
            #include <stdio.h>
            //设置文件指针的偏移
            int fseek(FILE *stream, long offset, int whence);
               参数:
                stream:文件指针
                offset:相对于whence的偏移量
                whence:偏移的起始位置
                    SEEK_SET, 文件的起始位置
                    SEEK_CUR, 文件指针当前的位置
                    SEEK_END, 文件的末尾
                返回值:成功返回0,失败返回-1 ;
    文件指针的定位
            //定位文件指针的当前位置
            long ftell(FILE *stream);
            参数:
                stream:文件指针
            返回值:成功返回文件指针当前位置,失败 -1 ;
3.4关闭文件:
   #include <stdio.h>
            //关闭打开的文件
            int fclose(FILE *stream);
3.5时间获取
获取标准时间:
            #include <time.h>
            time_t time(time_t *tloc);
            参数:
                tloc:存放标准时间的地址         
            返回值:成功返回标准时间,失败返回-1 ,并且设置错误号。    
    获得字符串时间
            char *ctime(const time_t *timep);
            参数:
                timep:标准时间 
            返回值:成功返回字符串时间指针,失败NULL  
    获得当地的时间
            struct tm *localtime(const time_t *timep);
            参数:
                timep:标准时间             
            返回值:成功结构体指针,失败返回NULL。
               struct tm {
                   int tm_sec;    /* Seconds (0-60) 秒数*/
                   int tm_min;    /* Minutes (0-59) 分钟*/
                   int tm_hour;   /* Hours (0-23) 小时*/
                   int tm_mday;   /* Day of the month (1-31)日期 */
                   int tm_mon;    /* Month (0-11) 实际月份 =tm_mon+1  */
                   int tm_year;   /* Year - 1900 实际年份=tm_year+1900 */
                   int tm_wday;   /* Day of the week (0-6, Sunday = 0)一周中的第几天 */
                   int tm_yday;   /* Day in the year (0-365, 1 Jan = 0)一年中的第几天 */
                   int tm_isdst;  /* Daylight saving time 冬令时间或者夏令时间*/
               };
    将当地时间转换成字符串时间。
    
            char *asctime(const struct tm *tm);
            参数:
                tm:当地时间结构体地址                
            返回值:成功返回字符串时间指针,失败NULL

二、Linux文件I/O编程

1、文件描述符

1.1文件描述符概述:

Linux操作系统是基于文件的概念,文件是以字符序列构成的信息载体,所以我们可以将I/O设备当作文件来处理。因此与磁盘上普通文件进行交互所用的同一系统调用可以直接使用I/O设备。内核区分和引用特定的文件便需要使用到文件描述符。

文件描述符是有以特点:


         1- 文件描述符是一个非负整数
        2- 对于内核而言所有打开的文件都由文件描述符引用
        3- 当打开或者创建文件的时候,内核会向进程返回一个最小的没有被占用的整数。作为文件描述符。
        4- 当读写文件的时候,需要用到open、create返回该文件描述符
1.2文件描述符的拷贝和重定向
#include <unistd.h>

     int dup(int oldfd);
      参数:
          oldfd:要拷贝的文件描述符
      返回值:成功返回新的文件描述符,失败-1 。
            
    文件描述符的重定向(捆绑)
            int dup2(int oldfd, int newfd);
            参数:
                oldfd,newfd:文件描述符 
                
            返回值:成功返回newfd的值,失败返回-1 ;
        注意:
            在dup2之前关闭newfd描述的文件

2.程序开机自启动的三个文件

所以创建文件,其文件描述符从3开始排序

3.虚拟文件系统

 Linux系统成功的关键因素之一就是具有与其他操作系统和谐共存的能力。Linux的文件系统由两层架构构成:虚拟文件操作系统(VFS),和各种具体的文件系统。

VFS就是将各种具体的文件系统的公共部分抽取出来,形成抽象层,是系统内核的一部分。通过VFS就对用户屏蔽了底层文件系统的实现的细节和差异。

可以通过命令查看系统支持哪些文件系统:

cat /proc/filesystems

 VFS在Linux系统中位置:

4.文件IO的操作

4.1文件io的打开
打开文件
            #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);
            
            参数:
                pathname:文件路径
                flags:打开方式
                    O_RDONLY :只读
                    O_WRONLY :只写
                    O_RDWR   :读写        这三个参数互斥

            -------------------------------------------------
                    O_CREAT :如果文件不存在则创建,并使用第三个参数设置权限
                    O_EXCL  :在使用O_CREAT创建文件的时候,如果文件已经存在,则返回错误信息,
                            可以用来检测文件是否存在
                    O_APPEND:以追加方式打开文件
                    O_TRUNC :如果文件已经存在,那么打开文件时先删除文件原有的数据。                   
                    
                    
                mode:创建文件的权限
                
                mode = mode & (~uamsk)
            返回值:成功返回文件描述符,失败返回-1
            
            r:       O_RDONLY
            r+:     O_RDWR
            
            w:       O_WRONLY|O_CREAT|O_TRUNC,0666
            w+:      O_RDWR|O_CREAT|O_TRUNC,0666
            
            a:      O_WRONLY|O_CREAT|O_APPEND,0666
            a+:     O_RDWR|O_CREAT|O_APPEND,0666
4.2文件IO的读写
1>读:
            #include <unistd.h>

            ssize_t read(int fd, void *buf, size_t count);
            参数:
                fd:文件描述符
                buf:内存首地址
                count:实际读取的个数,最多只能读buf-1 个字符
            返回值:成功返回实际读取到的个数,到达文件末尾返回0,
                    失败返回-1 ,并且设置错误号。                   
2>写:
            #include <unistd.h>

            ssize_t write(int fd, const void *buf, size_t count);
            参数:
                fd:文件描述符
                buf:内存首地址
                count:实际写入的个数,最多只能读buf-1 个字符 
            返回值:成功返回实际写入到的个数,失败返回-1 ,并且设置错误号。
4.3文件的关闭
  #include <unistd.h>
  int close(int fd);
4.4文件的定位
#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
    参数:
    fd:文件描述符
        offset:相对于whence的偏移量
        whence:偏移的起始位置
        SEEK_SET, 文件的起始位置
        SEEK_CUR, 文件指针当前的位置
        SEEK_END, 文件的末尾               
 返回值:成功返回偏移量,失败返回-1 

5文件锁

当文件已经共享,多个文件操作同一文件,有时会存在资源竞争的问题,Linux通常采用给文件上锁来解决。

1>fcntl函数:可以施加建议性锁,强制性锁,还有记录锁 。
可以对各种打开文件进行操作,不仅可以管理文件锁,还可以获取和设置文件相关标志位,以及复制文件描述符

#include <unistd.h>
 #include <fcntl.h>
 int fcntl(int fd, int cmd, ... /* arg */ );
    参数:    
        fd文件描述符
        cmd:    
                F_GETLK:检查文件锁状态
                F_SETLK:设置lock描述的文件锁
                F-SETLKW:对F_SETLK的阻塞版本(W表述wait())
        返回值:
            成功返回 0
            出错 -1
如果cmd和锁操作相关,则第三个参数类型为struct *flock其定义如下:

         struct flock {
               ...
               short l_type;    /* Type of lock: F_RDLCK,
                                   F_WRLCK, F_UNLCK */
               short l_whence;  /* How to interpret l_start:
                                   SEEK_SET, SEEK_CUR, SEEK_END */
               off_t l_start;   /* Starting offset for lock */
               off_t l_len;     /* Number of bytes to lock */
               pid_t l_pid;     /* PID of process blocking our lock
                                   (set by F_GETLK and F_OFD_GETLK) */
               ...
           };

flock结构体成员含义:

        l_type:
               F_RDLCK    读取锁(共享锁)
               F_WRLCK    写入锁(排斥锁)
               F_UNLCK    解锁
 

        l_start    
            加锁区域在文件中相对位移量(字节),与lwhence值一起决定加锁区域的起始位置
         l_len
            加锁区域长度
          l_pid
             具有阻塞当前进程的锁,其持有的进城后的进程号存放在l_pid中,仅由F_GETLK返回。
  


三、Linux一切皆文件

1.1文件属性

相对函数进行具体了解可以 man +函数名。如果在编程时需要查看函数可直接将光标放到函数名字上,再        ESC后,再:shift+k跳转到相对的函数库。按两次q回到vim编写函数。

            #include <sys/types.h>
            #include <sys/stat.h>
            #include <unistd.h>

            int stat(const char *pathname, struct stat *statbuf);
            int fstat(int fd, struct stat *statbuf);
            int lstat(const char *pathname, struct stat *statbuf);
        
        三个函数的返回:若成功则为0,若出错则为-1,并且设置errno.
        给定一个pathname的情况下:
         stat函数返回一个与此命名文件有关的信息结构
         fstat函数获得已在描述符filedes上打开的文件的有关信息
         lstat函数类似于stat,但是当命名的文件是一个符号连接时,
           lstat返回该符号连接的有关信息,而不是由该符号连接引用
        的文件的信息。
           struct stat {
               dev_t     st_dev;         /* ID of device containing file */
               ino_t     st_ino;         /* Inode number inode编号*/
               mode_t    st_mode;        /* File type and mode 文件类型以及权限*/
               nlink_t   st_nlink;       /* Number of hard links 硬链接数*/
               uid_t     st_uid;         /* User ID of owner 文件拥有者的用户ID*/
               gid_t     st_gid;         /* Group ID of owner 文件所属组的组ID*/
               dev_t     st_rdev;        /* Device ID (if special file) */
               off_t     st_size;        /* Total size, in bytes 文件大小 */
               blksize_t st_blksize;     /* Block size for filesystem I/O */
               blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */

               /* Since Linux 2.6, the kernel supports nanosecond
                  precision for the following timestamp fields.
                  For the details before Linux 2.6, see NOTES. */

               struct timespec st_atim;  /* Time of last access 最后一次访问时间*/
               struct timespec st_mtim;  /* Time of last modification 最后一次修改时间*/
               struct timespec st_ctim;  /* Time of last status change 最后一次状态改变时间 */

           #define st_atime st_atim.tv_sec      /* Backward compatibility */
           #define st_mtime st_mtim.tv_sec
           #define st_ctime st_ctim.tv_sec
           };
            //通过用户ID获得用户信息
             #include <sys/types.h>
             #include <pwd.h>
             struct passwd *getpwuid(uid_t uid);
               struct passwd {
                   char   *pw_name;       /* username */
                   char   *pw_passwd;     /* user password */
                   uid_t   pw_uid;        /* user ID */
                   gid_t   pw_gid;        /* group ID */
                   char   *pw_gecos;      /* user information */
                   char   *pw_dir;        /* home directory */
                   char   *pw_shell;      /* shell program */
               };
            //通过组ID获得组的信息
            #include <sys/types.h>
            #include <grp.h>
            struct group *getgrgid(gid_t gid);
                struct group {
                   char   *gr_name;        /* group name */
                   char   *gr_passwd;      /* group password */
                   gid_t   gr_gid;         /* group ID */
                   char  **gr_mem;         /* NULL-terminated array of pointers
                                              to names of group members */
               };

2.目录操作

目录也是文件:可以对目录进行打开关闭,读写操作。

    1. 打开目录
            #include <sys/types.h>
            #include <dirent.h>

            DIR *opendir(const char *name);
            参数:
                name:目录名字
            返回值:成功返回DIR类型的指针,失败返回NULL
    2. 读取目录
            #include <dirent.h>

            struct dirent *readdir(DIR *dirp);
            返回值:成功返回一个结构体指针,失败返回NULL
               struct dirent {
                   ino_t          d_ino;       /* Inode number 索引的节点号*/
                   off_t          d_off;       /* Not an offset; see below 在目录文件中的偏移*/
                   unsigned short d_reclen;    /* Length of this record 文件名的长度*/
                   unsigned char  d_type;      /* Type of file; not supported
                                                  by all filesystem types 文件类型*/
                   char           d_name[256]; /* Null-terminated filename 文件名*/
               };
    3. 关闭目录
            #include <sys/types.h>
            #include <dirent.h>
            int closedir(DIR *dirp);  
    4. 修改程序当前的工作目录
            #include <unistd.h>

            int chdir(const char *path);
            参数:
                path:程序运行更改后的目录
            返回值:成功返回0,失败-1 ,并且设置错误号

下一篇文章:多进程的生老病死,收尸,不想死

Linux多任务编程(一)_距喜喜编译器的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/apple_71040140/article/details/132403401