17、文件IO详解及实例

 

 

 

   上篇文章已经讲过了文件系统的一些基本的概念,这里首先对文件IO进行详细的学习,文件IO也称为系统调用IO,是操作系统为"用户态"运行的进程和硬件交互提供的一组接口,即操作系统内核留给用户程序的一个接口,按照操作系统的结构划分,Linux系统自上而下依次是:用户进程、Linux内核、物理硬件。其中Linux内核包括系统调用接口和内核子系统两部分。Linux内核处于“承上启下”的关键位置,向下管理物理硬件,向上为操作系统和应用程序提供接口,这里的接口就是系统调用。

下面依次介绍linux系统调用函数: 

1.open() 

  调用open()函数可打开或创建一个文件,其函数原型如下:

#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)  

   如果失败,返回值为-1

参数解析: pathname是要打开或者创建的文件名。flags  文件打开时候的选项, O_RDONLY以只读方式打开文件。O_WRONLY以只写方式打开文件。 O_RDWR以读、写方式打开文件。
这三个选项是必选的!


下列是参数flags 所能使用的旗标:
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件。上述三种旗标是互斥的,也就是不可同时使用,但可与下列的旗标利用OR(|)运算符组合。
O_CREAT 若欲打开的文件不存在则自动建立该文件。
O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。
文件若不存在则建立该文件,否则将导致打开文件错误。
此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。
O_NOCTTY 如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。
O_TRUNC 若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的资料也会消失。
O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
O_NDELAY 同O_NONBLOCK。
O_SYNC 以同步的方式打开文件。
O_NOFOLLOW
如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。
O_DIRECTORY 如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。

mode  只有创建文件时才使用此参数,指定文件的访问权限。模式有:

  S_IRWXU 00700 权限,代表该文件所有者具有可读、可写及可执行的权限。

  S_IRUSR 或S_IREAD, 00400权限,代表该文件所有者具有可读取的权限。

  S_IWUSR 或S_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。

  S_IXUSR 或S_IEXEC, 00100 权限,代表该文件所有者具有可执行的权限。

  S_IRWXG 00070权限,代表该文件用户组具有可读、可写及可执行的权限。

  S_IRGRP 00040 权限,代表该文件用户组具有可读的权限。

  S_IWGRP 00020权限,代表该文件用户组具有可写入的权限。

  S_IXGRP 00010 权限,代表该文件用户组具有可执行的权限。

  S_IRWXO 00007权限,代表其他用户具有可读、可写及可执行的权限。

  S_IROTH 00004 权限,代表其他用户具有可读的权限

  S_IWOTH 00002权限,代表其他用户具有可写入的权限。

  S_IXOTH 00001 权限,代表其他用户具有可执行的权限。

返回值

  若所有欲核查的权限都通过了检查则返回文件描述符,表示成功,只要有一个权限被禁止则返回-1。

错误代码

  EEXIST 参数pathname 所指的文件已存在,却使用了O_CREAT和O_EXCL标志位。

  EACCESS 参数pathname所指的文件不符合所要求测试的权限。

  EROFS 欲测试写入权限的文件存在于只读文件系统内。

  EFAULT 参数pathname指针超出可存取内存空间。

  EINVAL 参数mode 不正确。

  ENAMETOOLONG 参数pathname太长。

  ENOTDIR 参数pathname不是目录。

  ENOMEM 核心内存不足。

  ELOOP 参数pathname有过多符号连接问题。

  EIO I/O 存取错误。

  示例:使用函数open()打开文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc ,char *argv[])
{
    int fd=0;
    if(argc!=2)
    {
        printf("Your Input error!\n");
        exit(1);
    }
    fd=open(argv[1],O_RDONLY);
    if(fd<0)
    {
    perror("open()");
    exit(1);
    }
    else
    {
    printf("Open successful!\n");
    printf("The file Descriptor is %d\n",fd);
    }
    exit(0);
}

程序编译运行过程如下:

 2.close()

  调用函数close()可关闭一个打开的文件,其函数原型如下:

#include<unistd.h>

int close(int fd);

关闭已打开文件时会释放该进程加在该文件中上的所有记录锁。当一个进程终止时,内核自动关闭其所有已打开的文件。很多程序都利用这一功能而不是在程序中用函数close()关闭已打开的文件。

 

3.read()

  调用函数read()可从打开的文件中读取数据,其函数原型如下:

#include<unistd.h>

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

  如果读取成功,则返回读取到的字节数;如果已达到文件末尾,则返回0;如果读取失败,则返回-1.

  实际读取到的字节数会少于要求指定读取的直接输取的字节数的情况有以下几种:

         1、 读普通文件时,在读到要求字节数之前已经达到了文件结尾。eg:若在达到文件尾端之前有30个字节,而要求读50个字节,则read返回30,下次在调用read时将返回0;

         2、 当从终端设备读时, 通常一次最多读一行。

         3、 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。

         4、 当从管道FIFO读时,如若管道包含的字节少于所需求的数值,那么read将只返回实际可用的字节数。

         5、 当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录。

         6、 当一信号造成中断,而已经读了部分数据量时,一种处理方式操作系统可以认为该系统调用失败,并将errno设置为EINTR;另一种处理方式是允许该系统调用成功返回,返回值是已经接收到的数据量。

  读取操作从文件的当前偏移量处开始,在成功返回之前,该偏移量会增加实际读到的字节数。

下面写一个程序来对文件内容进行实际读取,编写源码如下

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>



#define BUFFERSIZE 1024



typedef int fid_t;



int main(int argc,char **argv)

/*第一个int argc,是记录你输入在命令行上的字符串个数;

第二个*argv[]是个指针数组,存放输入在命令行上的命令(字符串)。

*/

{

    fid_t fid=0;

    char *path=NULL;//定义路径指针

    char *buffer=NULL;//定义文件读取指针

    if(argc<2)//如果命令行字符串少于两个,说明没有输入路径

    {

    printf("Hadn't Input Filename!\n");

    exit(1);

    }

    buffer=(char*)malloc(BUFFERSIZE*sizeof(char));//计算出所需内存空间

    memset(buffer,0,BUFFERSIZE);//分配内存

    while(*++argv)

/*

argv[]是一个字符数组.



argv[0]:指向程序的全路径名



argv[1]:指向在DOS命令行中执行程序名后的第一个字符串。



argv[2]:指向第二个字符串。



*/

    {

        path=*argv;//路径指针指向指定路径

        printf("Filename :%s\n",path);//打印出指向的路径和文件名

        if((fid=open(path,O_RDONLY))==-1)//如果打开出错

        {

            perror("open():");//就输入信息和现在errno所对应的错误一起输出

            printf("\n");

            continue;

        }

    while(read(fid,buffer,BUFFERSIZE)>0)//当文件没读完时

    {

        printf("%s",buffer);//打印文件内容

        memset(buffer,0,BUFFERSIZE);//分配缓冲区大小

    }

    close(fid);//关闭文件

    printf("\n");

    }

    free(buffer);//释放内存空间

    buffer=NULL;//释放指针

    return 0;

}

 运行程序可以读取指定文件夹下的指定文件。

 

4.write()

   调用函数write()可向已打开的文件中写入数据,其函数模型如下:

#include<unistd.h>

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

  函数write()返回值通常与参数nbytes的值相同,否则表示出错。函数write()出错的常见原因是磁盘已满,或者是超过了进程文件给定的长度限制。

下面是一个示例,将一个文件中的内容读取出来复制到另外一个文件中去

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFFERSIZE 1024


typedef int fid_t;



int main()
{
    fid_t fd1=0;
    fid_t fd2=0;
    int readBytes=0;
    char *buffer=NULL;    
      buffer=(char*)malloc(BUFFERSIZE*sizeof(char));//计算出所需内存空间
      memset(buffer,0,BUFFERSIZE);//分配内存
    fd1=open("log.txt",O_RDONLY);//这里默认打开的所当前文件所在目录下的文件
    if(fd1<0)
    {
        perror("open(fd1)");//如果出错,显示出错原因
        exit(1);
    }
    fd2=open("log11.txt",O_RDWR|O_CREAT|O_EXCL,0644);//创建一个log11.txt文件,这里默认保存文件在本地目录下
    if(fd2<0)
    {
        perror("open(fd2)");
        exit(1);
    }
       while(readBytes=read(fd1,buffer,BUFFERSIZE)>0)//当文件没读完时
        {
        if(write(fd2,buffer,BUFFERSIZE)<0)//将读取的buffer里的数据写入进去,如果BUFFER写入错误
        {
        perror("write(fd2)");//显示出错原因
        exit(1);
        }
    }
    if(readBytes<0)
    {
        perror("read(fd1)");//如果读取报错,现实报错远迎
        exit(1);
    }
    close(fd1);//关闭文件被复制的文件
    close(fd2);//关闭复制到的文件
    exit(0);
}

下面是程序运行的结果,可以看到一个生成的log11.txt文件

打开文件可以看到如下复制后的结果

 

5.lseek()

   每个文件都有一个与之相关联的文件偏移量,用以度量文件起始位置到当前位置的字节数。通常情况下,读写操作都是从当前文件偏移量处开始的,读写完成后,文件偏移量会自动增加所读写的字节数。打开一个文件后,该文件偏移量默认设置为0.

  调用函数lseek()可显式的为打开的文件设置文件偏移量,其函数原型如下:

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

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

  函数lseek()仅将当前文件的文件偏移量记录在内核中,并不会引起任何I/O的操作,该文件偏移量用于下一次读写操作。

  函数执行成功,返回文件当前的文件偏移量;函数执行失败,返回-1。

在函数原型中,参数offet和whence搭配使用,具体含义如下:

  whence值为SEEK_SET时,则将该文件的文件偏移量设置为文件起始位置加offset个字节;

  whence值为SEEK_CUR时,则将该文件的文件偏移量设置为文件当前位置加offset个字节;

  whence值为SEEK_END时,则将该文件的文件偏移量设置为文件末尾位置加offset个字节;

 文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写入将写入将会增加文件长度,并在文件中构成一个空洞,即位于文件中单没有被写过的字节都被读为0的部分。

 下面是一个相关的示例,首先打开文本log.txt,然后将偏移量定位到离开始处10字节处,写入字符串“0123456”。

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

int main(void)
{
    int fd =-1;
    ssize_t size = -1; 
    off_t offset = -1; 

    char buf[]="0123456";
    char filename[]="log11.txt";
    
    fd = open(filename,O_RDWR);
    if(-1==fd)
    {   
        printf("Open file %s failure,fd:%d",filename,fd);
        return -1; 
    }   
    offset = lseek(fd,10,SEEK_SET);
    if(-1==offset)
    {   
        printf("lseek file %s failure,fd:%d",filename,fd);
        return -1; 
    }   
    size = write(fd,buf,strlen(buf));
    if(size!=strlen(buf))
    {   
        printf("write file %s failure,fd:%d",filename,fd);
        return -1; 
    }   
    close(fd);
    return 0;
}

编译执行后,打开log11.txt文件后,会发现文件变成了如下的样子

6、fcntl()

  fcntl()函数用于获得和改变已经打开文件的属性。

  其函数原型如下:

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd);
int fcnt(int fd, int cmd, long arg);
int fcnt(int fd, int cmd, struct flock *lock);

参数说明:

  fd:文件描述符

  cmd:操作命令

  arg:供命令使用的参数

  lock:同上

返回值:

  fcntl()的返回值与参数cmd有关,具体功能为:

如果出错,所有命令返回-1,如果成功则返回某个其他值。下列三个命令有特定的返回值:

    F_DUPFD:返回新的文件描述符

    F_GETFD:返回相应标志

    F_GETFL,F_GETOWN:返回一个正的进程ID或负的进程组ID

fcntl()函数有5种功能:

  1.复制一个现有的描述符(cmd = F_DUPFD)。

  2.获得/设置文件描述符标记(cmd = F_GETFD或F_SETFD),

  3.获得/设置文件状态标记(cmd = GETFL或F_SETFL)

  4.获得/设置异步I/O所有权(cmd = F_GETOWN或F_SETOWN)

  5.获得/设置记录锁(cmd = F_GETLK,F_SETLK或F_SETLKW)

详细的介绍请参考另一篇博文:httpc://blog.csdn.net/pbymw8iwm/article/details/7974789

下面通过一个示例来对这个函数进行理解,源码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc,char **argv)
{
    int val=0;
    if(argc!=2)
    printf("Function Input Error!\n"),exit(1);
    
    val=fcntl(atoi(argv[1]),F_GETFL,0);//返回argv[1]所表述的文件的文件标识符
//atoi(argv[1])这个是文件标识符,atoi是字符转为整数形式,第二个F_ GETFL是命令,第三个0是可选命令参数
    if(val<0)
    {
    perror("fcntl()");
    exit(1);
    }
    switch(val&O_ACCMODE)
    {
    case O_RDONLY:
    printf("Read Only!\n");
    break;
    case O_WRONLY:
    printf("write Only!\n");
    break;
    case O_RDWR:
    printf("Read write!\n");
    break;
    default:
    printf("Uknow Access MOde!\n");
    exit(2);
    }
    if(val&O_APPEND)
    printf(",Append");
    if(val&O_NONBLOCK)
    printf(",Nonblock");
    if(val&O_SYNC)
    printf(",synchronous");
    printf("\n");
    return 0;
}

 程序编译运行输出结果

运行过程中涉及到了一个重定向的定义,这里进行一个大概的讲解

./a.out 0 < /dev/tty
read only
./a.out 1 > temp.foo
write only
./a.out 2 2>>temp.foo
write only, append
./a.out 5 5<>temp.foo
read write

1) 标准输入0作为参数传递给a.out,
   < /dev/tty 这个虚拟tty文件的输入被重定向到了a.out的标准输入
   也就是a.out的标准输入就变成了/dev/tty
   其实可以写成   ./a.out 0 0</dev/tty
2)第二个同理
   可以写成    ./a.out 1 1>temp.foo 
   所以标准输出绑定到了文件temp.foo
   相当于在文件描述符1上打开了temp.foo且是只读的模式
   向标准输出写东西就写到了文件temp.foo
3) 第三个同理
   在文件描述符2上打开了文件temp.foo 而2是标准出错,用了>>表示以追加的模式打开了文件temp.foo
   所以取2的属性就取的是打开文件temp.foo的属性,而这个时候系统已经把文件的状态改了打开且追加的模式
4)同理
   在5上以读写模式打开文件,so取文件描述5的属性就是取文件的属性,那么read and write  就可以理解了

 

当然,fcntl还可以进行一些其他的操作,比如打开文件log.txt时设置为O_RDWR,此时文件的偏移量位于文件开头,修改状态值的时候增加O_APPEND项,此时文件的偏移量移到文件末尾,写入字符串FCNTL,然后关闭。

 源码如下:

#include<unistd.h>

#include<sys/types.h>

#include<fcntl.h>

#include<stdio.h>

#include<string.h>



int main(void)

{

    int flags = -1; 

    char buf[] = "FCNTL";

    int fd = open("log.txt",O_RDWR);

    flags  = fcntl(fd, F_GETFL, 0); 

    

    flags |= O_APPEND;

    flags = fcntl(fd, F_SETFL, flags);

    if(flags<0)

    {   

        printf("failure to use fcntl\n");

        return -1; 

    }   



    write(fd, buf, strlen(buf));

    close(fd);

    return 0;

}

7、stat()函数

  在程序设计的时候经常要用到文件的一些特征值,如文件的所有者,文件的修改时间、文件的大小等。函数stat()用来获得指定文件的属性,其有四个变种stat()函数、fstat()函数、lstat()函数和fstatat(),其函数原型如下

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

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

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

int fstatat(int dirfd,const char *pathname, struct stat *buf,int flags);

函数的第一个参数时文件描述的参数,可以为文件描述符或文件的路径(含文件名),buf为指向struct stat结构体的指针,获得的状态从这个参数中传回。当函数执行成功时返回0,执行失败返回-1。

  函数fstat()获得已在描述符fd上打开文件的有关信息,函数lstat()类似于stat(),但是当命名文件是一个符号链接时,函数lstat()返回符号链接的有关信息,而函数stat()返回该符号链接引用文件的信息。

  函数fstatat()返回相对路径的文件统计信息,falgs用于控制是否跟随一个符号连接。当AT_SYMLINK_NOFOLLOW标识被设置是,函数fstatat()不会跟随符号链接,而是返回符号链接本身的信息,否则,默认情况下,返回的是符号连接所引用文件的信息。如果参数fd的值是AT_FDCWD,并且pathname是一个绝对路径,则参数dirfd就会被忽略,在这两种情况下,更具参数flags的取值,函数fstatat()就与stat()和lstat()的功能一样的。

  参数buf是一个指针,他指向一个我们必须提供数据的结构,数据会自动填充buf指向的结构。结构的实际定义可能哦她那个具体实现有所不同,但其具体形式如下:

struct stat {
        mode_t     st_mode;       //文件对应的模式,文件,目录等
        ino_t      st_ino;       //inode节点号
        dev_t      st_dev;        //设备号码
        dev_t      st_rdev;       //特殊设备号码
        nlink_t    st_nlink;      //文件的连接数
        uid_t      st_uid;        //文件所有者
        gid_t      st_gid;        //文件所有者对应的组
        off_t      st_size;       //普通文件,对应的文件字节数
        time_t     st_atime;      //文件最后被访问的时间
        time_t     st_mtime;      //文件内容最后被修改的时间
        time_t     st_ctime;      //文件状态改变时间
        blksize_t st_blksize;    //文件内容对应的块大小
        blkcnt_t   st_blocks;     //文件内容对应的块数量
      };

文件类型信息包含在结构stat的st_mode成员中,可以用这一点来确定文件类型,这些宏的参数都是在stat结构中的st_mode成员。

下面举一个实际例子来检测文件类型,源码如下:

 

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
    int count = 0;
    struct stat buf;
    char *ptr;

    for (count = 0; count < argc; count++)
    {
        printf("%s:", argv[count]);
        if (lstat(argv[count], &buf) < 0)//使用lstat查看文件属性
        {
            perror("lstat()");
            continue;
        }
        if (S_ISREG(buf.st_mode))
            ptr = "Regular";
        else if (S_ISDIR(buf.st_mode))
            ptr = "Directory";
        else if (S_ISCHR(buf.st_mode))
            ptr = "Character Special";
        else if (S_ISBLK(buf.st_mode))
            ptr = "Block special";
        else if (S_ISFIFO(buf.st_mode))
            ptr = "FIFO";
        else if (S_ISLNK(buf.st_mode))
            ptr = "Symbolic Link";
        else if (S_ISSOCK(buf.st_mode))
            ptr = "Socket";
        else
            ptr = "**Unknowen Mode**";
        printf("%s\n", ptr);

    }
    exit(0);
 }

这里进行一个说明

结构体stat的st_mode里就有文件类型的信息(st_mode同时包含文件类型和文件权限)。 文件类型包括以下几种

      1. 普通文件/regular file.最常用的文件类型。数据可以是文本也可以是二进制文件,对unix内核来说没有区别。

  2. 目录文件/directory file.这种文件包含了其他文件的名字和指向这些文件有关信息的指针。

  3. 块特殊文件/block special file.这种文件类型提供对设备带缓冲的访问。每次访问以固定长度为单位进行。

  4. 字符特殊文件/ character special file这种文件类型提供对设备不带缓冲的访问。每次访问长度可变。

  5. FIFO这种类型文件用于进程间通信,也成为命名管道。

  6. 套接字 / socket这种文件类型用于进程间的网络通信。

  7. 符号链接 / sysbolic link这种类型文件指向另一个文件。

文件类型测试的宏对于一个文件,可以用下面的宏来确定文件类型。这些宏的参数是 stat中的st_mode成员。

  S_ISREG() 测试是否是普通文件S_ISDIR() 测试是否是目录文件S_ISBLK() 测试是否是块特殊文件S_ISCHR() 测试是否是字符特殊文件S_ISFIFO() 测试是否管道S_ISSOCK() 测试是套接子S_ISLNK() 测试是否是链接文件

8、mmap()、munmap()与msync()

mmap()函数将普通文件映射到内存中,普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。 mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。

mmap()映射后,让用户程序直接访问设备内存,相比较在用户控件和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关。

其函数原型如下

#include <sys/mman.h>

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)

参数说明:

  start:映射区的起始地址,通常为NULL(或0),表示由系统自己决定映射到什么地址。

  length:映射数据的长度,即文件需要映射到内存中的数据的大小。

  prot:映射区保护方式,取一下某个值或者它们的组合:

    PROT_EXEC:映射区可被执行

    PROT_READ:映射区可读取

    PROT_WRITE:映射区可写入

    PROT_NONE:映射区不可访i问

  flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合:

    MAP_FIXED:如果参数start指定了用于需要映射到的地址,而所指的地址无法成功建立映射,则映射失败。通常不推荐使用此设置,而将start设置为null(或0),由系统自动选 取映射地址。

    MAP_SHARED:共享映射区域,映射区域允许其他进程共享,对映射区域写入数据将会写入到原来的文件中。

    MAP_RIVATE:对映射区域进行写入操作时会产生一个映射文件的复制,即写入复制(copy on write),而读操作不会影响此复制。对此映射区的修改不会写回原来的文件,即不会影响原来文件的内容。

    MAP_ANONYMOUS:建立匿名映射。映射区不与任何文件关联,而且映射区无法与其他进程共享。

    MA_DENYWRITE:对文件的写入操作将被禁止,不允许直接对文件进行操作。

    MAP_LOCKED:将映射区锁定,防止页面被交换出内存。

    参数flags必须为MAP_SHARED或者MAP_PRIVATE二者之一的类型。MAP_SHARED类型表示多个进程使用的是一个内存映射的副本,任何一个进程都可对此映射进行修改,其他的进程对其修改是可见的。而MAP_PRIVATE则是多个进程使用的文件内存映射,在写入操作后,会复制一个副本给修改的进程,多个进程之间的副本是不一致的。

  fd:文件描述符,一般由open()函数返回。

  offse:被映射数据在文件中的起点。

 

mmap()执行成功后返回映射区的起始地址,执行失败返回-1.

 

对应的munmap()函数的作用是解除mmap()函数的映射关系。

其函数原型为

#inlcude <sys/mman.h>

int munmap(void *start, size_t length);

munmap()函数的作用是解除mmap()函数的映射关系。参数start时mmap()函数成功后的返回值,即映射的内存地址;length为映射区的长度。

函数执行成功返回0,执行失败返回-1。

 

msync()函数被称为刷新变化函数,一般来说,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作,可以通过调用msync()函数来实现磁盘文件内容与共享内存区中的内容一致,即同步操作。其函数原型为:

#inlcude <sys/mman.h>

int msync ( void * start , size_t length, int flags) 
参数说明:
  start:映射区的起始地址,即mmap()函数的返回值。
  length:映射空间的大小
  flags:刷新参数:
    MS_ASYNC:异步,调用会立即返回,不等到更新的完成。
    MS_SYNC:同步,调用会等到更新之后返回。
    MS_INVALIDATE:通知使用该共享区域的进程,数据已经改变。在共享内容更改之后,使得文件的其他映射失效,从而使得共享该文件的其他进程去重新获取最新值。
函数执行成功返回0,失败返回-1。
 
下面说一下建立文件的内存映射的流程:
  首先使用open()函数打开一个文件,当操作成功后会返回一个文件描述符;
  使用mmap()函数将此文件描述符所代表的文件映射到一个地址空间,如果映射成功,会返回一个映射地址执政;
  对文件的操作可以通过mmap()映射得到的地址来进行,包括读数据、写数据、偏移等,与一般的指针操作相同,不过要注意不要进行越界访问;
  当对文件操作完毕后,需要使用munmap()函数将mmap()映射的地址取消;
  使用close()函数关闭文件。
 
最后以一个实际例子对其进行深入理解
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<string.h>
#include<stdio.h>
#define FILELENGTH 800

int main(void)
{
    int fd = -1; 
    char buf[] = "Linux Command line and shell Scripting bible";
    char buf2[]= "a";
    char *addr = NULL;
    ssize_t size = -1; 
    off_t cur_pos = 0;
    //打开文件,将文件的长度缩小为0
    fd = open("mmap.txt",O_RDWR|O_CREAT, S_IRWXU);
    if(-1==fd)
    {   
        return -1; 
    }   
    cur_pos = lseek(fd,FILELENGTH-1, SEEK_SET); 
    size = write(fd,buf2,strlen(buf2));//随意写入一个字符,此时文件长度为800
    if(1==size)
    {   
        printf("write success\n");
    }   
    
    //将文件mmap.txt中的数据段从开头到1M的数据映射到内存中,对文件的操作立刻显示在文件上,可读写.
    addr = (char *)mmap(NULL, FILELENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 
    if((char *)-1 == addr)
    {   
        printf("mmap failure !\n");
        close(fd);
        return -1; 
    }   
    //将buf中的字符串复制到映射区域中,起始位置为addr偏移16
    memcpy(addr +16 ,buf ,strlen(buf));
    munmap(addr,FILELENGTH);//取消内存映射
    close(fd);//关闭文件
    return 0;
}

编译运行

打开生成文件,可以看到如下结果:

9、ioctl()

octl()函数通过对文件描述符的发送命令来控制设备。

其函数原型为:

#include <sys/ioctl.h>

int ioctl(int d, int rquest, ...);

ioctl()函数通过对文件描述符发送特定的命令来控制文件描述符所代表的设备。参数d时一个已经打开的设备。通常情况下ioctl()函数出错会返回-1,成功返回0或者大于1的值,取决于对应设备的驱动程序对命令的处理。

使用ioctl()像其他的系统调用一样:打开文件,发送命令,查询结果。ioctl()函数像一个杂货铺,对设备的控制通常都通过这个函数来执行。具体对设备的操作方式取决于设备驱动程序的编写。

 

详细介绍可看如下链接:

ioctl()函数详解

ioctl函数详细说明(网络)

 

10、access()

调用access()可以获取进程实际身份对文件的访问权限,其函数原型如下:

#include <unistd.h>

int access(const char *pathname, int mode);

#include <fcntl.h>
#include < unistd.h>

int faccessat(int dirfd,const char *pathname ,int mode ,int flags);

参数mode的取值在头文件unistd.h中的预定义分别为

#define R_OK 4 /* 测试文件读权限 */
#define W_OK 2 /*测试文件写权限*/ 
#define X_OK 1 /*测试文件执行权限*/
#define F_OK 0 /*测试文件存在*/

参数pathname可以为绝对路径和相对路径(相对于当前目录);

参数dirfd=AT_FDCWD时,默认相对路径起点为当前目录;

参数flags=AT_EACCESS时,检查进程有效身份的访问权限,而非进程的实际身份。

下面是一个测试文件是否存在的例子,源码如下

#include <stdio.h>

#include <unistd.h>

int file_exists(char *filename);

int main(void)

{

printf("Does NOTEXIST.FIL exist: %s\n",

file_exists("NOTEXISTS.FIL") ? "YES" : "NO");

return 0;

}

int file_exists(char *filename)

{

return (access(filename, 0) == 0);

}

编译运行结果如下:

 此时,一些常用的文件IO即学习完毕了。

参考链接:

linux 文件属性

unix/linux文件属性及其系统调用

【Linux】文件操作系统调用

关于linux重定向的解析

linux重定向简介

 

posted @ 2018-02-26 14:55  noticeable  阅读(880)  评论(0编辑  收藏  举报