《嵌入式linux应用程序开发标准教程》笔记——6.文件IO编程
前段时间看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所指向结构中的其他信息保持不变。
F_SETLK:根据flockptr设置锁。如果试图建立一把按上述兼容性规则并不允许的锁,则f c n t l立即出错返回,此时e r r n o设置为E A C C E S或E A G A I N F_SETLKW: F_SETLK的阻塞版. |
|
flockptr |
l_type:
写入锁是互斥锁,读取锁是共享锁。 如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁
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直接上,并查询结果,确定是否上锁成功。 |
【注意】:
!记录锁继承性等问题
- 进程、文件关闭与锁的关系 由于锁是在进程信息里存放的,故关闭进程或者关闭文件,该文件的锁有自动关闭。
- 由fork产生的子进程不继承父进程的锁。
- 执行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,fcntl void 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 = ;
st_lock.l_len = ;
st_lock.l_pid = -;
if( fcntl(fd,F_GETLK,&st_lock) < ) // 判断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) < )
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,);
if( fd< )
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();
}
终端窗口运行加读锁:
st_lock.l_type:2 // 可上读锁,所以F_GETLCK返回F_UNLCK,其他域不变
st_lock.l_whence:
st_lock.l_start:
st_lock.l_len:
st_lock.l_pid:-
Read lock is set by pid fcntl GETLK err. // F_GETLCK+F_UNLCK会返回错误
st_lock.l_type:
st_lock.l_whence:
st_lock.l_start:
st_lock.l_len:
st_lock.l_pid:-
Un lock is set by pid
FINISH
另一个终端窗口运行加读锁:
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
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> |
|
原型 |
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 |
|
返回值 |
大于 0:成功,返回准备好的文件描述符的数目 |
|
注意事项 |
监视的文件发生变化,就会返回>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 1024 int 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)) < )
printf("\r\n open in1 err");
if( (fd_in2=open("in2",O_RDWR|O_NONBLOCK)) < )
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 = ;
timeout.tv_usec = ;
while( select(fd_max+, &readfds,NULL,NULL,&timeout) > )
{
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 < )
printf("\r\nread %d err",fd_read);
else
{
printf("\r\nrcv something from %d",fd_read);
if( fd_read == STDIN_FILENO )
{
if(buf[] == '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();
}
终端1:
mknod in1 p
cat > in1 终端2:
mknod in2 p
cat > in2 6666 终端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 1024 int main(int args, char *argv[])
{
int fd_in1,fd_in2,fd_read;
struct pollfd st_pollfd[];
char buf[BUFF_SIZE];
int read_len;
char i; // open in1&in2
if( (fd_in1=open("in1",O_RDWR|O_NONBLOCK)) < )
printf("\r\n open in1 err");
if( (fd_in2=open("in2",O_RDWR|O_NONBLOCK)) < )
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[].fd = fd_in1;
st_pollfd[].events = POLLIN;
st_pollfd[].fd = fd_in2;
st_pollfd[].events = POLLIN;
st_pollfd[].fd = STDIN_FILENO;
st_pollfd[].events = POLLIN; while( poll (&st_pollfd, , *) > )
{
if( st_pollfd[].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=;i<;i++)
{
if( st_pollfd[i].revents&POLLIN == POLLIN )
{
read_len = read(st_pollfd[i].fd,buf,BUFF_SIZE);
if( read_len < )
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[] == '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();
}
《嵌入式linux应用程序开发标准教程》笔记——6.文件IO编程的更多相关文章
- 《嵌入式linux应用程序开发标准教程》笔记——8.进程间通信
, 8.1 概述 linux里使用较多的进程间通信方式: 管道,pipe和fifo,管道pipe没有实体文件,只能用于具有亲缘关系的进程间通信:有名管道 named pipe,也叫fifo,还允许无亲 ...
- 《嵌入式linux应用程序开发标准教程》笔记——9.多线程编程
线程是轻量级进程,创建线程的开销要比进程小得多,在大型程序中应用广泛. 9.1 线程概述 进程包含自己的代码.数据.堆栈.资源等等,创建和切换的开销比较大: 线程是轻量级的进程,调度的最小单元,同一个 ...
- 《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发
进程是系统资源的最小单元,很重要. 7.1 linux进程的基本概念 定义:一个程序的一次执行过程,同时也是资源分配的最小单元.程序是静态的,而进程是动态的. 进程控制块:linux系统用进程控制块描 ...
- 嵌入式Linux应用程序开发详解------(创建守护进程)
嵌入式Linux应用程序开发详解 华清远见 本文只是阅读文摘. 创建一个守护进程的步骤: 1.创建一个子进程,然后退出父进程: 2.在子进程中使用创建新会话---setsid(): 3.改变当前工作目 ...
- 嵌入式Linux应用程序开发环境搭建记录
2016年2月 参考资料: OK210软件手册(Linux版).pdf Ubuntu下Qt4.7.1编译环境配置说明.pdf 我阅读了以下内容: OK210软件手册(Linux版).pdf 第七章 O ...
- 嵌入式linux应用程序调试方法
嵌入式linux应用程序调试方法 四 内存工具 五 C/C++代码覆盖.性能profiling工具 四 内存工具 您肯定不想陷入类似在几千次调用之后发生分配溢出这样的情形. 许多小组花了许许多多时间来 ...
- gdbserver远程调试嵌入式linux应用程序方法
此处所讲的是基于gdb和gdbsever的远程调试方法.环境为:PC机:win7.虚拟机:10.04.下位机:飞嵌TE2440开发板. 嵌入式linux应用程序的开发一般都是在linux里面编写好代码 ...
- Linux入门-程序开发
Linux程序开发 linux程序总体上来说是分两部分的: 1. 底层驱动程序开发: 2.应用层应用程序开发: 驱动程序 一般情况下驱动是跟内核与硬件有关系的,编程语言是C语言,需要懂一些硬件的知识, ...
- 嵌入式linux系统应用开发
关于嵌入式系统 平时大家说的嵌入式其实范围比较广的,是一种软硬件可裁剪,以应用为中心开发的专用系统,硬件平台可以是单片机,或者以ARM系列的处理器.单片机一般直接裸奔程序,不过现在有了好多基于单片 ...
随机推荐
- [題解]BZOJ_1260_塗色
簡單的區間dp,結果竟然寫掛了......還掛的很徹底......狗屎 如果區間左右端點相等,那麼不需要在多花一次去刷,對 f [ i+1 ] [ j ],f [ i ] [ j-1 ]取個min, ...
- C# 基础之字段与属性
1.属性是字段的扩展 2.根据面向对象封装思想,字段最好设为private(私有),这样有利于防止客户端对字段的篡改,从而保证了成员的完整性 3.访问类中私有字段,C#提供了属性,用来对字段进行灵活的 ...
- 【bzoj1718】Redundant Paths 分离的路径
1718: [Usaco2006 Jan] Redundant Paths 分离的路径 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 964 Solve ...
- UVA11988:悲剧文本(模拟链表)
You’re typing a long text with a broken keyboard. Well it’s not so badly broken. The only problem wi ...
- linux进行文件vim编辑时没有退出文件直接关闭出现E325: ATTENTION Found a swap file by the name "/usr/local/php/etc/.php.ini.swp"
E325: ATTENTIONFound a swap file by the name "/usr/local/php/etc/.php.ini.swp" ow ...
- 排序算法(C语言+Python版)宝宝再也不怕面试官写排序算法了
直接插入排序 过程: 1. 数据可分看成两个部分,前面的数据是有序的 2. 从后面的数据取出一个元素,插到前面有序数据的合适位置 从右端开始查找,到找到比此元素大的时候,则此元素向后移动,以空出多余的 ...
- ReferenceError: password is not defined
报错提示位置at c:\Users\Administrator\WebstormProjects\blogtest\routes\index.js:19:16 原因是我这个password没有定义,p ...
- ES-windos环境搭建(2)
下载 打开elasticsearch官网.选择downloads. 选择elasticsearch,并单击Download. 这里我们不选择最新版,而是选择靠前的版本,下拉选择past release ...
- Android5.0以上版本录屏实现
我录屏的方式是分别录制音频和视频,最后合并成mp4格式,比较麻烦,因为网上完整的教程比较少,所以我打算写一个完整版的,照着我的代码写完之后,至少是能够实现功能的,而不是简单的介绍下用法. 1既然是录制 ...
- 导入maven的java web项目运行报错找不到Spring监听器
本地成功运行的一个maven项目,在另一台机器复制下来并导入,运行时报错: java.lang.ClassNotFoundException: org.springframework.web.cont ...