Linux系统编程1-----文件I/O(对于文件)

1.基本介绍:Linux中一切都是“文件”。文件只能必须先打开才能访问,文件打开方式只有三种,只读,只写,读写模式。文件打开后是通过唯一表示符来引用的,其是打开文件关联的元数据到文件本身的映射,在Linux内核中,文件被一个整数描述,成为文件描述符,大多数对文件操作都是涉及文件描述符来说明。

2.访问文件之前,必须要打开文件,通过系统调用,使用open()函数

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

int open(const char* name,int flags);
int open(const char* name,int flags,mode_t mode);

如果open调用成功,返回文件描述符,name:文件的起始位置(可以是路径),打开方式有参数flages确定。

    flages:其可以是一位或者是多个标志位的或运算组合,下面是常见的几种标志位

    O_RDONLY:只读, O_WRONLY:只写, O_RDWR:读或者写 

int fd;

fd=open("/home/user/test",O_RDONLY);
 if(fd==-1){
//error
}

如上是只以读的模式打开文件进行执行。注意,创建文件时,用户对每一个文件都有属性,假如用户对该文件只有读权限,就不能以写的方式打开它,反之依然,可以通过命令修改文件用户权限.其他更多flags不多用,自行查找。

补充,还可以用open()函数来创造新的文件,再创建新文件时,要附加mode参数,一般如下

//前提 用户要有写权限
 int fd=open("name",O_WRONLY|O_CREAT|O_TRUNC,mode_t mode)

mode一般带S_IRWXU,即文件有读,写,读或写的权限。(本质是写入磁盘的权限由mode参数和用户文件创建的掩码进行运算获得。)上述操作等价于(mode取0666或0644),mode即指明创造文件时的所有者,组用户,所有用户三个方面的权限是怎样的。

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

int creat(const char* name,mode_t,mode);

3.write(),read()函数

对于网络编程来说,此两个函数不能用socket专属文件描述符去配合设置并使用,始终成为非阻塞I/O,但是可在系统编写文件的文件描述符下(open()调用),使之成为阻塞,非阻塞I/O。

 先介绍read()函数:

#include <unistd.h>
ssize_t read(int fd,void* buf,size_t len);

调用open()创建的套接字时,会从fd指向文件当前的偏移量开始读len个字节,存入到buf的内存中,多次调用,fd指向文件的文件指针会自己移动,返回实际读取到的字节数,不一定真的会读len个字节。当文件读到末尾或者网络编程中对面连接的文件描述符已经关闭时,返回为0.不是阻塞。

对于返回-1时,可以查看errno(配合<errno.h>,<string.h>)查看情况,或errno=EAGAIN,表示当前没有数据可读,表示阻塞(只会在非阻塞设置下)

或errno!=EINTR或者!=EAGAIN,表明发生更严重的错误,可以关闭套接字了.(也适用于SOCKET类传递接受客户端或者服务端的消息)

errno!=EINTR 表示在读取任何字节之前接收到信号。调用可以重新执行

一般read可以循环读,比如:

ssize_t ret;
while((len!=0)&&(read=read(fd,buf,len))!=0){
    if(ret!=-1){
        if(errno==EINTR)
            continue;
            perror("read");
            break;
    }
    len-=ret;//总长度减少
    buf+=ret;//存入数组的指针进行移动
}

非阻塞(O_NONBLOCK)下需要判断errno==EINTR或 errno==EAGAIN.

SOCKET专属入下:

现在介绍write()函数:

#include <unistd.h>

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

对于文件来说是从fd指向的文件中,将buf指向的指针开始写入count字节当中去,返回写入的实际字节数。只需要注意两点。1:以O_APPEND方式去写入文件,可以确保文件指针总是在文件末尾追加写,可以防止多线程的竞争问题。2.write()可以不用考虑非阻塞,不常用。

补充WRITE()的行为:当发起write()调用时,首先会将数据拷贝到缓冲区,内核收集数据后会进行数据排序,然后再写入磁盘(页回写机制)。所以可能会发生,数据还未存入磁盘,系统崩溃。所以就有了同步I/O,以确保数据能在何时写入磁盘。

同步I/O:

#include <unistd.h>

int fsync(int fd);


上述函数可以确保和文件描述符所指向的文件相关的·数据·都写到磁盘上,文件描述符必须以写方式打开,并且该函数会回调创建的时间戳或者其他文件属性。但函数fdatasync()却不会进行回调,执行比fsync()快。但上述两个函数不能同步更新文件目录项,所以需要对文件目录进行同步调用上述两个函数。

open()函数的标志位:O_SYNC。使得文件操作符对应的WRITE(),READ()。都是确保同步性,同时也是开销最大的,这个要注意。

int fd;
fd=open(file,O_WRONLY|O_SYNC);

所以WRITE(),READ()都是直接从内核与用户态相交互。

关闭文件:

#include <unistd.h>

int close(int fd);

close函数可以关闭当前进程的文件描述符与FD之间的映射,但注意关闭文件操作并非意味着该文件的数据已经被写入磁盘了,需要运用到同步操作确定。

下面是一般的文件打开调用过程:(操作系统内有详细解释)

4.lseek()函数:

一般情况下,I/O是线性的,但有时候我们需要跳跃式读取文件,所以lseek()系统调用能将文件描述符的位置指针设置为定制,lseek()只更新文件位置。

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

off_t lseek(int fd,off_t pos,int t);

lseek()函数的行为依靠参数t: 

 SEEK_CUR: 将文件位置设置为当前值再加上偏移量pos(可为负数),pos=0则返回当前位置的值。

SEEK_END:将文件位置设置为文件长度加上pos偏移量,pos=0说明设置为文件指针在末尾。

SEEK_SET:将文件位置设置为POS值,如果POS=0,则设置成文件开始。

文件读和写操作公用一个文件指针.例:

#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main(){
    int fd,n;
    char mag[]="hello world nide";
    char ch;

    fd=open("lseek.txt",O_RDWR|O_CREAT,0644);//这时 文件指针指向开始位置 0
    if(fd<0){
        perror("open error");
        exit(1);
    }
    write(fd,mag,strlen(mag));//文件指针移动strlen(mag)个单位,但后面没东西了 
    lseek(fd,0,SEEK_SET);//回到文本最开始
    while((n=read(fd,&ch,1))){
        if(n<0){
            perror("read errror");
            exit(1);
        }else{
            write(STDOUT_FILENO,&ch,n);
        }
    }
    close(fd);
    return 0;
}

如果注释掉lseek函数 那么执行while循环读是无法读到任何东西的,因为现在文件指针指向nide后面,没有数据,只有通过lseek移动文件指针到最开始才行。

用SEEK-END可以扩充文件大小,但对处于文件末尾的文件指针使用写入数据的操作是没有意义的。

5.I/O多路复用。

1).select()函数(如何运用见网络编程笔记)

#include <sys/select.h>

int select (int n,fd_set* readfd,fd_set* writefd,fd_set* ex,struct timeval* timeout);

FD_CLR(int fd,fd_set* set);
FD_ISSET(int fd,fd_set* set);
FD_SET(int fd,fd_set* set);
FD_ZERO(fd_set* set);

select函数的2,3,4参数是等待不同事件的。2号参数是监听是否有数据可读,3号参数是监听是否有某个写操作是无阻塞完成的(即在监听事件内是否有通过文件描述符发生写事件),4号参数是监听监听的文件描述符是否有异常或者是出现外带数据等。成功返回时,每个集合都修改成只包含相应事件类型的I/O就绪的文件描述符。第一个参数是监听集合中,最大的文件描述符数+1,第五个参数是设置的超时事件。这里要注意,每一次SELECT()调用后,都要重置超时事件。

//FD_ZERO() 对fd_set变量进行全部归零(删除全部文件描述符)

fd_set writefd;
FD_ZERO(&writefd);

//FD_SET()往指定集中加入描述符以供监听 FD_CLR() 从指定集中删除指定文件描述符

FD_SET(fd,&writefd);
FD_CLR(fd,&writefd);

//FD_ISSET() 用于检查监听的文件描述符是否准备就绪。准备就绪返回非零值

if(FD_ISSET(fd,&readfds))
//fd is no blocking to read

select()实现可移植sleep()函数功能。通常如下操作可代替sleep()函数。

struct timeval tv;
tv.tv_sec=0;
tv.tv_usec=500;

select(0,NULL,NULL,NULL,&tv);//代替sleep函数

  简单实例:

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

const int timeout=5;
const int BUF_SIZE=1024;

int main(){
    struct timeval tv;
    fd_set readfd;
    FD_ZERO(&readfd);
    tv.tv_sec=timeout;
    tv.tv_usec=0;

    FD_SET(STDIN_FILENO,&readfd);

    int ret=select(STDIN_FILENO+1,&readfd,NULL,NULL,&tv);

    if(ret==-1){
        perror("select");
        exit(1);
    }else if(ret==0){
        std::cout<<"error timeout"<<std::endl;
        return 0;
    }

    if(FD_ISSET(STDIN_FILENO,&readfd)){
     char buf[BUF_SIZE];
    int len=read(STDIN_FILENO,buf,sizeof(buf));
     if(len==-1){
        perror("read");
        exit(1);
     }
     if(len){
       buf[len]='\0';
       std::cout<<buf<<std::endl;
     }
    }
    return 0;
}
/*通俗易懂理解,&readfd中成员就是有数据传递过来了,我可以发生读事件 writefd中成员就是有数据可以写了,我可以给对面写数据,这里对应标准数据就是从标准输入中读取事件*/

2.)poll()函数

#include <poll.h>

int poll(struct pollfd* fds,nfds_t nfds,int timeout);

第一个参数是pollfd类的数组,里面存入的是需要监听的SOCKET,第二个参数是pollfd结构体的数量,第三个是超时时间(毫秒)。

pollfd结构体如下

struct pollfd {
int fd ;//要监视的文件描述符
short events;//监视文件描述符事件的位掩码
short revent;//结果事件的位掩码
}

内核在返回时会设置revents变量,event变量中的请求的所有的事件都可能在revent变量中返回。

  对应事件情况如下:

若给上述编号,从1开始,1等价于2和3的组合,1和4的组合等价于select的读事件,POLLIN,POLLOUT本身针对I/O事件就是非阻塞可读/写事件。标识位可多种组合,不会发生冲突,就是一个文件描述符可能发生多种情况的事件(对准备结果分类讨论),比如监听某个文件描述符是否可读写:POLLIN|POLLOUT。

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

const int timeout=5;


int main(){
    struct pollfd fds[2];
    int res;

    fds[0].fd=STDIN_FILENO;
    fds[0].events=POLLIN;
    
    fds[1].fd=STDOUT_FILENO;
    fds[1].events=POLLOUT;

    res=poll(fds,2,timeout*1000);
    if(res==-1){
        perror("poll");
        exit(1);
    }
    if(!res){
        std::cout<<"time out"<<std::endl;
    }

    if(fds[0].revents&POLLIN){
        std::cout<<"stdin is readable"<<std::endl;
    }
    if(fds[1].revents&POLLOUT){
        std::cout<<"stdout is writeable"<<std::endl;
    }

    return 0;

}

在真正的网络编程中,pollfd结构体会重复被调用,但是每一次事情的结果之后,内核都会将revents清空。

标准输入对应的是读事件(对内读),标准输出对应的是写事件(对外写)。

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值