《嵌入式linux应用程序开发标准教程》笔记——6.文件IO编程
发布日期:2021-05-19 03:27:47 浏览次数:22 分类:原创文章

本文共 12779 字,大约阅读时间需要 42 分钟。

  前段时间看APUE,确实比较详细,不过过于详细了,当成工具书倒是比较合适,还是读一读这种培训机构的书籍,进度会比较快,遇到问题时再回去翻翻APUE,这样的效率可能更高一些。

  《嵌入式linux应用程序开发标准教程》的前几章没必要看了,都是写浅显的知识点,从第六章文件IO编程开始记录笔记。后期再根据APUE的内容进行补充和扩展。

 

 一、linux系统调用及API

  1. 系统调用

  linux分为内核空间和用户空间,用户空间无法直接访问内核空间。内核通过系统调用为用户提供服务,很精简,大约250个左右。大致可分为:进程控制、进程间通信、文件系统控制、系统控制、存储管理、网络管理、socket控制、用户管理等几类。

  2. C库API

  C库提供若干API,遵循一定的标准,供用户使用。

  用户可以直接调用系统调用,也可以调用C库提供的API。

 

 二、linux中文件及文件描述符概述

  linux主要有4中文件:普通文件、目录文件、链接文件、设备文件。

  linux使用文件描述符操作文件,尤其对于用户态来说更是如此,文件描述符是一个非负的整数,是个索引值,linux打开文件时动态分配,分配时,优先分配未使用的最小描述符。打开文件时,内核返回给进程1个文件描述符,读写时进程用此描述符操作文件。 

  进程打开时,默认会打开三个文件描述符,三个文件默认均指向终端:

  STDIN_FILENO: 0,标准输入

  STDOUT_FILENO: 1,标准输出

  STDERR_FILENO: 2,标准错误

 

 三、底层文件IO操作

  3.1 基本文件操作

     3.1.1 函数说明

  5个基本函数,不带缓冲,不属于ANSI C,属于POSIX标准。

  open、read、write、lseek、close, 见APUE相关笔记。

  3.2 文件锁

   3.2.1 共享问题

 

  如上图,进程信息中包含“打开文件的当前文件偏移量”,由进程各自维护。若进程1打开了文件并定位到文件尾部——>切换到进程2,进程2定位到文件尾部并写了100个字节——>在回到进程1,写了10个字节——>则结果是该文件的最后100个字节中,前10个是进程1写的,并覆盖了原来进程2写的前10个字节,而后90个是进程2写的。

  事与愿违,故有的场合期望对文件操作时独占的,所以引入的记录锁。

  只要多个进程操作同一个文件,就应该上锁。

   3.2.2 fcntl()

需要头文件

#include <sys/types.h>

#include <unistd.h>

#include <fcnt1.h>

#include <sys/select.h>

原型

int fcnt1(int  filedes, int  cmd,.../* struct flock * flockptr * / ) ;

描述

除了记录锁以外,还有好多其他的功能

形参

filedes

文件描述符

cmd

记录锁相关的

F_GETLK:根据第三个参数的情况测试是否可以上锁,要上的锁由flockptr描述.决定由f l o c k p t r所描述的锁是否被另外一把锁所排斥(阻塞)。如果存在一把锁,它阻止创建由 f l o c k p t r所描述的锁则这把现存的锁的信息写到 f l o c k p t r指向的结构中。如果不存在这种情况,则除了将 l _ t y p e设置为F _ U N L C K之外, f l o c k p t r所指向结构中的其他信息保持不变。

  • 若该文件可以上锁(当前未上锁),则flockptr->l_type为F_UNLK, flockptr的其他项不变;
  • 若不能上锁则将该文件已经上锁的信息通过flockptr返回此时不返回错误。
  • 若flockptr->l_type为F_UNLCK(要上的锁,不能使这个),则返回错误

 F_SETLK:根据flockptr设置锁。如果试图建立一把按上述兼容性规则并不允许的锁,则f c n t l立即出错返回,此时e r r n o设置为E A C          C E SE A G A I N 

 F_SETLKW: F_SETLK的阻塞版.

flockptr

l_type:

  •  F_RDLCK:读取锁,共享锁,加读锁时,文件必须包含读打开
  •  F_WRLCK:写入锁,排斥锁,加写锁时,文件必须包含写打开
  •  F_UNCLK:解锁

写入锁是互斥锁,读取锁是共享锁。

如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁

 

 l_start和l_whence:其实保护位置(文件偏移)和相对位置(SEEK_CUR/SET/END)。与,lseek相同。

 len想保护的长度,若len=0,则表示从l_start和l_whence决定的位置开始,到文件结尾的最大长度。

若想保护整个文件,则可以l_start = 0,l_whence=SEEK_SET, len=0。

保护区域可以超过尾部,但不能在起始位置之前。

返回值

若成功则依赖于c m d,若出错则为- 1时

注意事项

用F_GETLK测试,再用F_ SETLK或F_ SETLKW上锁不是原子操作,可能会有问题!

应该用F_ SETLK直接上,并查询结果,确定是否上锁成功。

 【注意】:

    !记录锁继承性等问题

  1. 进程、文件关闭与锁的关系 由于锁是在进程信息里存放的,故关闭进程或者关闭文件,该文件的锁有自动关闭。
  2. 由fork产生的子进程不继承父进程的锁。
  3. 执行exec后,新进程可以继承原来的锁。默认继承

     死锁举例

 

       如图,进程1锁文件1,进程2锁文件2。若进程1想锁文件2,且选择了阻塞方式,则进程1会处于阻塞状态,同理进程2也是。这样就构成了死锁。 

   使用方法:

   使用F_GETLK后再F_SETLK,不是原子操作,可能会出问题。可以直接用F_SETLK,然后判断返回值。

 

  3.2.3 例程

/* 6-2,fcntl test */#include <stdio.h>    // printf#include <stdlib.h>    // exit#include <unistd.h>#include <fcntl.h>    // open,fcntlvoid lock_set(int fd, short lock_type){    struct     flock st_lock;    st_lock.l_type = lock_type;    st_lock.l_whence = SEEK_SET;    st_lock.l_start = 0;    st_lock.l_len = 0;    st_lock.l_pid = -1;    if( fcntl(fd,F_GETLK,&st_lock) < 0 )        // 判断st_lock类型的锁能否上,测试类型,用F_GETLK然后再F_SETLCK,不是原子原子操作,会有问题,例程这么设计,主要是为了方便理解读写锁的互斥性        printf("\r\nfcntl GETLK err.");    // 如果能上,则l_type返回F_UNLCK,st_lock的其他域不变    // 如果不能上,则l_type返回目前的上锁状态,同时l_pid返回对该文件上锁的进程pid    if( st_lock.l_type !=  F_UNLCK )            {        if( st_lock.l_type == F_RDLCK )            printf("\r\nRead lock is already locked by pid %d",st_lock.l_pid );        if( st_lock.l_type == F_WRLCK )            printf("\r\nWrite lock is already locked by pid %d",st_lock.l_pid );    }    printf("\r\nst_lock.l_type:%d",st_lock.l_type);        printf("\r\nst_lock.l_whence:%d",st_lock.l_whence);        printf("\r\nst_lock.l_start:%d",st_lock.l_start);        printf("\r\nst_lock.l_len:%d",st_lock.l_len);        printf("\r\nst_lock.l_pid:%d",st_lock.l_pid);            st_lock.l_type = lock_type;    if( fcntl(fd,F_SETLKW,&st_lock) < 0 )                printf("\r\nfcntl SETLK err.");        switch( st_lock.l_type )    {    case F_RDLCK:        printf("\r\nRead lock is set by pid %d",getpid() );        break;    case F_WRLCK:        printf("\r\Write lock is set by pid %d",getpid() );        break;    case F_UNLCK:        printf("\r\nUn lock is set by pid %d",getpid() );        break;    defualt:        break;    }}int main(int args, char *argv[]){    int fd;    // open file    fd=open("hello",O_RDWR|O_CREAT,0644);    if( fd<0 )            printf("\r\nopen file err");        //lock_set(fd,F_RDLCK);        // 读锁    lock_set(fd,F_WRLCK);        // 写锁    getchar();            // 等待终端输入    lock_set(fd,F_UNLCK);    printf("\r\nFINISH\r\n");    exit(0);}

 

 

终端窗口运行加读锁:
st_lock.l_type:2          // 可上读锁,所以F_GETLCK返回F_UNLCK,其他域不变st_lock.l_whence:0st_lock.l_start:0st_lock.l_len:0st_lock.l_pid:-1Read lock is set by pid 7377fcntl GETLK err.          // F_GETLCK+F_UNLCK会返回错误st_lock.l_type:2st_lock.l_whence:0st_lock.l_start:0st_lock.l_len:0st_lock.l_pid:-1Un lock is set by pid 7377FINISH

另一个终端窗口运行加读锁:

  st_lock.l_type:2            // 读锁是共享锁,可以随便加,不受影响
  st_lock.l_whence:0
  st_lock.l_start:0
  st_lock.l_len:0
  st_lock.l_pid:-1
  Read lock is set by pid 7426     

  fcntl GETLK err.
  st_lock.l_type:2
  st_lock.l_whence:0
  st_lock.l_start:0
  st_lock.l_len:0
  st_lock.l_pid:-1
  Un lock is set by pid 7426
  FINISH

// 终端1先运行

st_lock.l_type:2
st_lock.l_whence:0
st_lock.l_start:0
st_lock.l_len:0
Write lock is set by pid 7517

fcntl GETLK err.
st_lock.l_type:2
st_lock.l_whence:0
st_lock.l_start:0
st_lock.l_len:0
st_lock.l_pid:-1
Un lock is set by pid 7517
FINISH

// 终端2后运行

Write lock is already locked by pid 7517   // 写锁互斥,释放后才能加锁
st_lock.l_type:1
st_lock.l_whence:0
st_lock.l_start:0
st_lock.l_len:0
Write lock is set by pid 7518          // 阻塞加锁,所以终端1释放后,马上加上了

fcntl GETLK err.
st_lock.l_type:2
st_lock.l_whence:0
st_lock.l_start:0
st_lock.l_len:0
st_lock.l_pid:-1
Un lock is set by pid 7518
FINISH

3.3 多路复用

3.3.1 IO模型

  • 阻塞IO,若IO没有完成相关功能,则进程挂起,直到相关数据到达后才返回。  管道、终端、网络设备的读写经常出现这种情况。
  • 非阻塞IO,IO操作不能完成,理解返回,该进程不睡眠
  • 多路转换,轮训各IO,超时等待,select和poll就属于此类
  • 信号驱动IO,signal

3.3.2 函数说明

 

需要头文件

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

原型

int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set *exeptfds, struct timeval *timeout)

描述

监测多个文件,这就是多路IO的由来

形参

numfds

该参数值为需要监视的文件描述符的最大值加 1

readfds

由 select()监视的读文件描述符集合

writefds

由 select()监视的写文件描述符集合

 

exeptfds

由 select()监视的异常处理文件描述符集合

 

timeout

NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止

具体值:struct timeval 类型的指针,若等待了 timeout 时间还没有检
测到任何文件描符准备好,就立即返回

struct timeval
{
long tv_sec; /* 秒 */
long tv_unsec; /* 微秒 */
}

返回值

大于 0:成功,返回准备好的文件描述符的数目
0:超时;-1:出错

注意事项

监视的文件发生变化,就会返回>0的数

注意:select会改变要监视的文件描述符集,如果需要连续监测,需要注意重新初始化该集合

 

select函数期望对文件描述符分类处理,处理的对象是 描述符的集合,需要使用相关宏定义

FD_ZERO(fd_set *set) 清除一个文件描述符集
FD_SET(int fd, fd_set *set) 将一个文件描述符加入文件描述符集中
FD_CLR(int fd, fd_set *set) 将一个文件描述符从文件描述符集中清除
FD_ISSET(int fd, fd_set*set)如果文件描述符 fd 为 fd_set 集中的一个元素,则返回非零值,可以用于调用 select()之后测试文件描述符集中的文件描述符是否有变化#include <sys/types.h>

#include <poll.h>

int poll( struct pollfd * fds, int numfds, int timerout );
参数:
  fds:描述需要对哪些文件哪种类型操作进行监控。
     struct pollfd
     {
        int fd;      // 需要监听的文件描述符  
        short events;   // 需要监听的事件
        short revents;  // 监听到的事件/已发生的事件
     }
     events可由若干定义好的宏定义描述:

        /* Event types that can be polled for. These bits may be set in `events'
          to indicate the interesting event types; they will appear in `revents'
          to indicate the status of the file descriptor. */
        #define POLLIN 0x001 /* There is data to read. */        文件中有数据可读
        #define POLLPRI 0x002 /* There is urgent data to read. */    文件中有紧急数据可读
        #define POLLOUT 0x004 /* Writing now will not block. */     可以向文件中写入数据

        #ifdef __USE_GNU
        /* These are extensions for Linux. */
        # define POLLMSG 0x400
        # define POLLREMOVE 0x1000
        # define POLLRDHUP 0x2000
        #endif

        /* Event types always implicitly polled for. These bits need not be set in
          `events', but they will appear in `revents' to indicate the status of
          the file descriptor. */
        #define POLLERR 0x008 /* Error condition. */           文件中出现错误
        #define POLLHUP 0x010 /* Hung up. */                与文件的链接被断开
        #define POLLNVAL 0x020 /* Invalid polling request. */      文件描述符非法  

  numfds:需要监听的文件个数,即第一个参数fds的个数
  timeout:超时时间,ms。如果<0,表示无限等待
返回值:
  成功:大于0,表示事件发生的pollfd个数
  0:超时
  -1:出错

注意事项:
  其实select在系统内部,也是由poll实现的,poll貌似更简单易用一些。

3.3.3 例程

用两个终端创建两个FIFO,第三个终端运行程序,监测两个FIFO的输入。

select:

/* 6-3,select */#include <stdio.h>    // printf#include <stdlib.h>    // exit#include <unistd.h>#include <fcntl.h>    // open,fcntl#include <sys/time.h>    // struct timeval#include <sys/select.h>#include <sys/param.h>    // MAX()#define BUFF_SIZE    1024int main(int args, char *argv[]){    int fd_max,fd_in1,fd_in2,fd_read;    fd_set readfds;    struct timeval timeout;    char buf[BUFF_SIZE];    int  read_len;    // open in1&in2    if( (fd_in1=open("in1",O_RDWR|O_NONBLOCK)) < 0 )        printf("\r\n open in1 err");        if( (fd_in2=open("in2",O_RDWR|O_NONBLOCK)) < 0 )        printf("\r\n open in1 err");    printf("\r\nin1 %d,in2 %d, STDIN_FILENO %d",fd_in1,fd_in2,STDIN_FILENO);    fflush(stdout);        // 把流flush,printf才能及时打印    // select     FD_ZERO(&readfds);    FD_SET(fd_in1,&readfds);    FD_SET(fd_in2,&readfds);    FD_SET(STDIN_FILENO,&readfds);    fd_max = MAX(MAX(fd_in1,fd_in2),STDIN_FILENO);    timeout.tv_sec = 60;    timeout.tv_usec = 0;    while( select(fd_max+1, &readfds,NULL,NULL,&timeout) > 0 )    {        if( FD_ISSET(fd_in1, &readfds) )            fd_read = fd_in1;        if( FD_ISSET(fd_in2, &readfds) )            fd_read = fd_in2;            if( FD_ISSET(STDIN_FILENO, &readfds) )                fd_read = STDIN_FILENO;            read_len = read(fd_read,buf,BUFF_SIZE);        if( read_len < 0 )            printf("\r\nread %d err",fd_read);        else        {            printf("\r\nrcv something from %d",fd_read);            if( fd_read == STDIN_FILENO )            {                if(buf[0] == 'q')                {                    printf("\r\nquit by user.");                    break;                }            }            else            {                buf[read_len] = '\0';                printf( "\r\nRcv data from %d:%s",fd_read,buf )    ;            }        }        // 因为select会改变readfds的值,所以想连续监测,要及时重新设置要监视的fd        FD_ZERO(&readfds);                FD_SET(fd_in1,&readfds);        FD_SET(fd_in2,&readfds);        FD_SET(STDIN_FILENO,&readfds);        fd_max = MAX(MAX(fd_in1,fd_in2),STDIN_FILENO);    }            printf("\r\n job done");    exit(0);}

 

终端1:mknod in1 pcat > in1111122223333终端2:mknod in2 pcat > in2444455556666

终端3运行上述程序,会显示终端1/2的输入,'q'则退出

 

poll实现相同的功能,执行效果与select差不多。

/* 6-4,poll */#include <stdio.h>    // printf#include <stdlib.h>    // exit#include <unistd.h>#include <fcntl.h>    // open,fcntl#include <sys/time.h>    // struct timeval#include <sys/select.h>#include <sys/param.h>    // MAX()#include <poll.h>#define BUFF_SIZE    1024int main(int args, char *argv[]){    int fd_in1,fd_in2,fd_read;    struct pollfd st_pollfd[3];    char buf[BUFF_SIZE];    int  read_len;    char i;    // open in1&in2    if( (fd_in1=open("in1",O_RDWR|O_NONBLOCK)) < 0 )        printf("\r\n open in1 err");        if( (fd_in2=open("in2",O_RDWR|O_NONBLOCK)) < 0 )        printf("\r\n open in1 err");    printf("\r\nin1 %d,in2 %d, STDIN_FILENO %d",fd_in1,fd_in2,STDIN_FILENO);    fflush(stdout);        // 把流flush,printf才能及时打印    // poll     st_pollfd[0].fd = fd_in1;    st_pollfd[0].events = POLLIN;    st_pollfd[1].fd = fd_in2;    st_pollfd[1].events = POLLIN;    st_pollfd[2].fd = STDIN_FILENO;    st_pollfd[2].events = POLLIN;    while( poll (&st_pollfd, 3, 60*1000) > 0 )    {        if( st_pollfd[0].revents&POLLIN == POLLIN )            fd_read = fd_in1;        if( FD_ISSET(fd_in2, &readfds) )            fd_read = fd_in2;            if( FD_ISSET(STDIN_FILENO, &readfds) )                fd_read = STDIN_FILENO;            for(i=0;i<3;i++)        {            if( st_pollfd[i].revents&POLLIN == POLLIN )            {                read_len = read(st_pollfd[i].fd,buf,BUFF_SIZE);                if( read_len < 0 )                    printf("\r\nread %d err",st_pollfd[i].fd);                else                {                    printf("\r\nrcv something from %d",st_pollfd[i].fd);                    if( st_pollfd[i].fd == STDIN_FILENO )                    {                        if(buf[0] == 'q')                        {                            printf("\r\nquit by user.");                            break;                        }                    }                    else                    {                        buf[read_len] = '\0';                        printf( "\r\nRcv data from %d:%s",st_pollfd[i].fd,buf )    ;                    }                }                            }                                    }    }            printf("\r\n job done");    exit(0);}

 

上一篇:《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发
下一篇:《UNIX环境高级编程》笔记——4.文件和目录

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2025年04月25日 20时52分42秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

elTable火狐浏览器换行 2023-01-24
15个Python数据处理技巧(非常详细)零基础入门到精通,收藏这一篇就够了 2023-01-24
2023年深信服、奇安信、360等大厂网络安全校招面试真题合集(附答案),让你面试轻松无压力! 2023-01-24
2024年全国程序员平均薪资排名:同样是程序员,为什么差这么多?零基础到精通,收藏这篇就够了 2023-01-24
0基础成功转行网络安全工程师,年薪30W+,经验总结都在这(建议收藏) 2023-01-24
100个电脑常用组合键大全(非常详细)零基础入门到精通,收藏这篇就够了 2023-01-24
10个程序员可以接私活的平台 2023-01-24
10个运维拿来就用的 Shell 脚本,用了才知道有多爽,零基础入门到精通,收藏这一篇就够了 2023-01-24
10条sql语句优化的建议 2023-01-24
10款宝藏编程工具!新手必备,大牛强烈推荐! 从零基础到精通,收藏这篇就够了! 2023-01-24
10款最佳免费WiFi黑客工具(附传送门)零基础入门到精通,收藏这一篇就够了 2023-01-24
15个Python数据分析实用技巧(非常详细)零基础入门到精通,收藏这一篇就够了 2023-01-24
15个备受欢迎的嵌入式GUI库,从零基础到精通,收藏这篇就够了! 2023-01-24
15个程序员常逛的宝藏网站!!从零基础到精通,收藏这篇就够了! 2023-01-24
1分钟学会在Linux下模拟网络延迟 2023-01-24
2023应届毕业生找不到工作很焦虑怎么办? 2023-01-24
2023最新版Node.js下载安装及环境配置教程(非常详细)从零基础入门到精通,看完这一篇就够了 2023-01-24
2023网络安全现状,一个(黑客)真实的收入 2023-01-24
2024 年需要了解的顶级大数据工具(非常详细)零基础入门到精通,收藏这一篇就够了 2023-01-24
2024 最新 Kali Linux 定制化魔改,完整版,添加常见60渗透工具,零基础入门到精通,收藏这篇就够了 2023-01-24