嵌入式-文件I/O

#include <stdio.h> 
#include <stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
  
int main(int argc, char **argv)
{
    struct stat     buf;
    if(argc != 2) { 
       printf("Usage: stat "); 
       exit(-1); 
    }
    if(stat(argv[1], &buf) != 0) { 
        printf("stat error."); 
        exit(-1); 
    }
    printf("#i-node:    %ld
", buf.st_ino);
    printf("#link:      %d
", buf.st_nlink);
    printf("UID:        %d
", buf.st_uid);
    printf("GID:        %d
", buf.st_gid);
    printf("Size        %ld
", buf.st_size);
    exit(0);
}

1.实现简单的文件内容复制的功能

memset函数按字节对内存块进行初始化,所以不能用它将int数组初始化为0和-1之外的其他值,一个字符一个字节的,方便初始化。

  如果用memset(a,1,20),就是对a指向的内存的20个字节进行赋值,每个都用数1去填充,转为二进制后,1就是00000001,占一个字节。一个int元素是4字节,合一起是0000 0001,0000 0001,0000 0001,0000 0001,转化成十六进制就是0x01010101,就等于16843009,就变成了对一个int元素的赋值了。

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    int srcid,dstid;
    unsigned char buf[128];
    int readlen,pos;
    memset(buf,0x00,128);//对于字符指针类型的,剩余的部分通常是不会为0的,防止出现野值
    srcid = open("/home/mm/open.c",O_RDONLY);
    if(srcid<0)
    {
        printf("打开文件出错 
");
        return 0;
    }
    dstid = open("/home/mm/open.c.bak",O_WRONLY|O_CREAT);
    if(dstid<0)
    {
        printf("打开文件出错 
");
        return 0;
    }
    pos = lseek(srcid,0,SEEK_CUR);
    printf("文件当前位置 %d 
",pos);
    while(readlen = read(srcid,buf,128)>0)//大于0说明文件还没有读完
    {
        write(dstid,buf,readlen);
        pos +=readlen;//修改读写指针位置
        lseek(srcid,pos,SEEK_SET);//设置读写指针位置
    }
 
    close(srcid);
    close(dstid);
}

针对上面例子的Makefile文档,上面文件目录为  /home/wxd/study/open.c

open:open.o
    gcc -o open open.o 
.PHONY:clean
clean:
    sudo rm -f *.o open open.c.bak ~*

read

read()是一个系统调用函数。用来从一个文件中,读取指定长度的数据到 buf 中。

使用read()时需要包含的头文件: <unistd.h>

函数原型:

  ssize_t read(int fd, void *buf, size_t count);

  ssize_t是系统头文件中 typedef 定义的数据类型,相当于 signed int。

参数:

  fd:要从中读取内容的文件的文件描述符。

  count:期望读取的文件字节数。

返回值:

  成功 返回读到的字节数; 已读到文件结尾返回 0; 出错返回 -1。

 

Write

 write()是一个系统调用函数,作用是将buf 中的内容写入到文件中。

使用write()函数时要包含头文件 <unistd.h>。

函数原型:

  ssize_t write(int fd, char *buf, size_t count)

参数:

  fd:要从中读取内容的文件的文件描述符。

  count:期望读取的文件字节数。

返回值:

  写入成功返回实际写入的字节数,出错返回-1。

  不得不提的是,返回-1的常见原因是:磁盘空间已满,超过了一个给定进程的文件长度。    

 

lseek()的作用是,设置文件内容的读写位置。

  每个打开的文件都有一个“当前文件偏移量”,是一个非负整数,用以度量从文件开始处计算的字节数。通常,读写操作都是从当前文件偏移量处开始,并使偏移量增加所读或写的字节数。默认情况下,你打开一个文件(open),除非指定O_APPEND参数,不然位移量被设为0。

使用lseek()需要包含的头文件:<sys/types.h>,<unistd.h>

函数原型:

  off_t lseek(int fd, off_t offset, int whence);

  off_t是系统头文件定义的数据类型,相当于signed int

参数:

  fd:是要操作的文件描述符

  whence:是当前位置基点。

    SEEK_SET,以文件的开头作为基准位置,新位置为偏移量的大小。

    SEEJ_CUR,以当前文件指针的位置作为基准位置,新位置为当前位置加上偏移量。

    SEEK_END,以文件的结尾为基准位置,新位置位于文件的大小加上偏移量。

  offset:偏移量,要偏移的量。可正可负(向后移、向前移);

返回值:

  成功返回相对于文件开头的偏移量。    出错返回-1

使用lseek获取文件大小:

 stat 的基本使用

stat:返回一个与此命

需要包含的头文件: <sys/types.h>,<sys/stat.h>,<unistd.h>

函数原型:

  int stat(const char *path, struct stat *buf);
      int fstat(int fd, struct stat *buf);
      int lstat(const char *path, struct stat *buf);

参数:

  对于stat() & lstat()来说path,是要查看属性的文件或目录的全路径名称

  对于fstat,fd 是要查看属性文件的文件描述符

  buf:指向用于存放文件属性的结构体,函数成功调用后,buf各个字段存放各个属性。

返回值 成:

  功返回0;      错误返回 -1;

给定一个文件:

  stat 函数获得一个与此命名文件有关的信息(到一个struct stat 类型的buf中) 。

  fstat 函数获得文件描述符 fd 打开文件的相关信息(到一个struct stat 类型的buf中) 。

  lstat 函数类似于 stat,但是当命名文件是一个符号连接时, lstat 获取该符号连接的有关信息,而不是由该符号连接引用文件的信息。

struct stat在系统头文件<stat.h>中,

fcntlselect函数详解

第一、fcntl函数详细使用      

fcntl有强大的功能,它能够复制一个现有的描述符,获得/设置文件描述符标记,获得/设置文件状态标记,获得/设置异步I/O所有权,获得/设置纪录锁。

当多个用户共同使用,操作一个文件的情况,Linux通常采用的方法就是给文件上锁,来避免共享资源产生竞争的状态。

fcntl文件锁有两种类型:建议性锁和强制性锁
        
建议性锁是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。
        
强制性锁是由内核执行的。当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。

         使用fcntl文件锁进行I/O操作必须小心:进程在开始任何I/O操作前如何去处理锁,在对文件解锁前如何完成所有的操作,是必须考虑的。如果在设置锁之前打开文件,或者读取该锁之后关闭文件,另一个进程就可能在上锁/解锁操作和打开/关闭操作之间的几分之一秒内访问该文件。当一个进程对文件加锁后,无论它是否释放所加的锁,只要文件关闭,内核都会自动释放加在文件上的建议性锁(这也是建议性锁和强制性锁的最大区别), 所以不要想设置建议性锁来达到永久不让别的进程访问文件的目的(强制性锁才可以)^_^;强制性锁则对所有进程起作用。

      可以用fcntl 函数改变一个已打开的文件的属性,可以重新设置读、写、追加、非阻塞等标志(这些标志称为File StatusFlag),而不必重新open 文件。
      #include <unistd.h>
      #include <fcntl.h>
      int fcntl(int fd, int cmd);
      int fcntl(int fd, int cmd, long arg);
      int fcntl(int fd, int cmd, struct flock *lock);

这个函数和open 一样,也是用可变参数实现的,可变参数的类型和个数取决于前面的cmd 参数。

文件锁包括了 建议性锁 和 强制性锁。

建议性锁要求每个上锁的文件的进程都要检查是否有锁存在,并且尊重已有的锁,在一般情况下,内核和系统都不使用建议性锁。

强制性锁是由内核执行的锁,当一个文件被上锁进行读写操作的时候,内核将阻止其他任何文件对其进行读写操作。每次读写操作都要检查是否有锁存在。

Linux中实现上锁的函数有lock()fcntl()

lock()
用于对文件施加建议性锁
fcntl()
用于对文件施加建议性锁和强制性锁都行。同时还可以对文件某一条纪录进行上锁,也就是记录锁。

记录锁分为 读取锁(共享锁,它能够使多个进程都能在文件的同一部分建立读取锁) 和 写入锁(排斥锁,在任何时刻只能有一个进程在文件的某部分建立写入锁。)。

fcntl
函数原型
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>

int fcntl(int fd,   //
文件描述符
          int cmd , //
不同的命令
          struct flock *lock) //
设置记录锁的具体状态

cmd
取值:
F_DUPFD  
复制文件描述符
F_GETFD  
获得fdclose-on-exec标志
F_SETFD  
设置close-on-exec标志
F_GETFL  
获得open设置标志
F_SETFL  
设置lock描述的标志
F_GETLK  
测试该锁是否被另外一把锁排斥
F_SETLKW
如果存在其他锁,则调用进程睡眠,如果捕捉到信号则睡眠中断
F_GETOWN
检索收到的SIGIOSIGURG信号的进程号或者进程组号
F_SETOWN
设置进程号或进程组号

这里的lock结构体如下:
struct flock
{
    short l_type;   /*F_RDLCK(
读取锁),F_WRLCK(写入锁),F_UNLCK(解锁)*/
    off_t l_start; /*
相对偏移量(字节)*/
    short l_whence; /*SEEK_SET ,SEEK_CUR ,SEEK_END */
    off_t l_len;    /*
加锁区域长度*/
    pid_t l_pid;
}

成功:0
出错:-1
提示:如果加锁整个文件通常的方法是将l_start设置为0l_whence设置为SEEK_SET, l_len设置为0


      
下面的例子使用F_GETFLF_SETFL这两种fcntl 命令改变STDIN_FILENO的属性上O_NONBLOCK 选项,实现非阻塞读终端的功能。

fcntl改变File Status Flag


             #include <unistd.h>
             #include <fcntl.h>
             #include <errno.h>
             #include <string.h>
             #include <stdlib.h>
             #define    MSG_TRY "try again "
             int main(void)
             {
                     char buf[10];
                     int n;
                     int flags;
                     flags = fcntl(STDIN_FILENO, F_GETFL);
                     flags |= O_NONBLOCK;
                     if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1)
           
   {
                             perror("fcntl");
                             exit(1);
                     }
             tryagain:
                     n = read(STDIN_FILENO, buf, 10);
                     if (n < 0)
                    {
                             if (errno == EAGAIN)
                            {
                                     sleep(1);
                                     write(STDOUT_FILENO, MSG_TRY,strlen(MSG_TRY));
                                     goto tryagain;
                             }
                             perror("read stdin");
                             exit(1);
                     }
                     write(STDOUT_FILENO, buf, n);
                     return 0;
             }

第二、select函数详细使用   

      select系统调用是用来让我们的程序监视多个文件句柄(file descriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。

       
文件在句柄在Linux里很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件句柄被创建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”man 2 open可以看到“open() and creat() return the new file descriptor”,其实文件句柄就是一个整数,看socket函数的声明就明白了:
int socket(int domain, int type, int protocol);
        
当然,我们最熟悉的句柄是012三个,0是标准输入,1是标准输出,2是标准错误输出。012是整数表示的,对应的FILE *结构的表示就是stdinstdoutstderr0就是stdin1就是stdout2就是stderr
比如下面这两段代码都是从标准输入读入9个字节字符:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
        char buf[10] = "";
        read(0, buf, 9); /*
从标准输入 0 读入字符 */
        fprintf(stdout, "%s ", buf); /*
向标准输出 stdout 写字符 */
        return 0;
}
/* **
上面和下面的代码都可以用来从标准输入读用户输入的9个字符** */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
        char buf[10] = "";
        fread(buf, 9, 1, stdin); /*
从标准输入 stdin 读入字符 */
        write(1, buf, strlen(buf));
        return 0;
}

        继续上面说的select,就是用来监视某个或某些句柄的状态变化的。select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
       
函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

struct timeval {
             long    tv_sec;         /* seconds */
             long    tv_usec;        /* microseconds */
         };


234三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds 传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:

fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */
struct timeval tv; /*
申明一个时间变量来保存时间 */
int ret; /*
保存返回值 */
FD_ZERO(&rdfds); /*
select函数之前先把集合清零 */
FD_SET(socket, &rdfds); /*
把要检测的句柄socket加入到集合里 */
tv.tv_sec = 1;
tv.tv_usec = 500; /*
设置select等待的最大时间为1秒加500毫秒 */
ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /*
检测我们上面设置到集合rdfds里的句柄是否有可读信息 */
if(ret < 0) perror("select");/*
这说明select函数出错 */
else if(ret == 0) printf("
超时 "); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
else { /*
说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
    printf("ret=%d ", ret); /* ret
这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */
    /*
这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */
    if(FD_ISSET(socket, &rdfds)) { /*
先判断一下socket这外被监视的句柄是否真的变成可读的了 */
        /*
读取socket句柄里的数据 */
        recv(...);
    }
}

注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:
/************
关于本文档********************************************
*filename: Linux
网络编程一步一步学-select详解
*purpose:
详细说明select的用法
*********************************************************************/
int sa, sb, sc;
sa = socket(...); /*
分别创建3个句柄并连接到服务器上 */
connect(sa,...);
sb = socket(...);
connect(sb,...);
sc = socket(...);
connect(sc,...);

FD_SET(sa, &rdfds);/*
分别把3个句柄加入读监视集合里去 */
FD_SET(sb, &rdfds);
FD_SET(sc, &rdfds);
在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:

int maxfd = 0;
if(sa > maxfd) maxfd = sa;
if(sb > maxfd) maxfd = sb;
if(sc > maxfd) maxfd = sc;

然后调用select函数:

ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */

同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:

FD_ZERO(&rdfds);
FD_SET(0, &rdfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(1, &rdfds, NULL, NULL, &tv); /*
注意是最大值还要加1 */
if(ret < 0) perror("select");/*
出错 */
else if(ret == 0) printf("
超时 "); /* 在我们设定的时间tv内,用户没有按键盘 */
else { /*
用户有按键盘,要读取用户的输入 */
    scanf("%s", buf);
}

 

 使用文件标准I/O进行复制文件:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
 
#define SRC_FILE_NAME "/home/mm/copy.c" /* 源文件名 */
#define DEST_FILE_NAME "/home/mm/copy.c.bak" /* 目标文件名文件名 */
 
int cp_file(char *sfile,char *dfile,u_int32_t uLen)
{
    FILE *sFile = NULL,*dFile = NULL;
    char *line = NULL;
    int tmpNO;
    if((sFile= fopen(sfile,"rb+")) == (FILE *)NULL)   //打开原文件
    {
        return -1;
    }
    if((dFile=fopen(dfile,"wb+")) == (FILE *)NULL)  //打开新文件
    {
        return -1;
    }
 
    line = (char *)malloc(uLen);
    if(line == NULL)
    {
        return -1;
    }
 
    memset(line,0,uLen);
 
    if(fread(line,sizeof(char),uLen,sFile) != uLen)     //读取原文件内容,如果文件很大,请分块读取
    {
        printf("updatefile:fopen error");
        fclose(sFile);
        free(line);
        return -1;
    }
 
 
    if(fwrite(line,sizeof(char),uLen,dFile) != uLen)      //写入新文件
    {
        printf("updatefile:fopen error");
        fclose(dFile);
        free(line);
        return -1;
    }
 
    tmpNO = fileno(dFile);
    fsync(tmpNO);                                 //刷新内核的块缓存
    fclose(sFile);
    fclose(dFile);
    free(line);
    return 0;
}
 
int main(void)
{
    struct stat buf;
    stat(SRC_FILE_NAME, &buf);
    if(cp_file(SRC_FILE_NAME,DEST_FILE_NAME,buf.st_size)<0)
        printf("copy file error");
    return 0;

}
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <unistd.h>
  4. #include <fcntl.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. int main()
  9. {
  10.     int srcid,dstid;
  11.     unsigned char buf[128];
  12.     int readlen,pos;
  13.     memset(buf,0x00,128);
  14.     srcid = open("/home/mm/open.c",O_RDONLY);
  15.     if(srcid<0)
  16.     {
  17.         printf("打开文件出错  ");
  18.         return 0;
  19.     }
  20.     dstid = open("/home/mm/open.c.bak",O_WRONLY|O_CREAT);
  21.     if(dstid<0)
  22.     {
  23.         printf("打开文件出错  ");
  24.         return 0;
  25.     }
  26.     pos = lseek(srcid,0,SEEK_CUR);
  27.     printf("文件当前位置 %d  ",pos);
  28.     while(readlen = read(srcid,buf,128)>0)
  29.     {
  30.         write(dstid,buf,readlen);
  31.         pos +=readlen;
  32.         lseek(srcid,pos,SEEK_SET);
  33.     }
  34.  
  35.     close(srcid);
  36.     close(dstid);
  37. }
原文地址:https://www.cnblogs.com/wddx5/p/12444297.html