Linux的IO系统调用函数open、read、write、stat

今天我们来看一下几个系统调用函数,以及他们的基本用法。

一、open

                                              int open ( char  *filename ,  int  flags , mode_t  mode );

            open 函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。

①filename    

            文件名

②flags

O_RDONLY 只读
O_WRONLY 只写
O_RDWR 可读可写
O_CREAT 如果文件不存在,就创建它的一个空文件
O_TRUNC 如果文件已经存在,就清空它
O_APPEND 在每次写操作前,设置文件位置到文件的结尾处,也就是追加操作

       flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的提示

③mode

        mode参数制定了新文件的访问权限位

掩码 描述

S_IRUSR

S_IWUSR

S_IXUSR

使用者(拥有者)能够读这个文件

使用者(拥有者)能够写这个文件

使用者(拥有者)能够执行这个文件

S_IRGRP

S_IWGRP

S_IXGRP

拥有者所在组的成员能够读这个文件

拥有者所在组的成员能够写这个文件

拥有者所在组的成员能够执行这个文件

S_IROTH

S_IWOTH

S_IXOTH

其他人(任何人)能够读这个文件

其他人(任何人)能够读写这个文件

其他人(任何人)能够执行这个文件

下面的函数说明如何以读的方式打开一个已存在的文件

                 int fd=open("foo.txt",O_RDONLY,0);

下面的函数说明如何打开一个已存在的文件,并在后面添加一些数据

                int fd=open("foo.txt",O_WRONLY|O_APPEND,0);

下面的函数经常用作创建一个新文件

                int fd=open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,0);


二、read、write

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

     read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的实际传送的字节数量。

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

     write函数从内存位置buf复制最多n个字节到描述符fd的当前文件位置。

下面的程序使用read和write调用一次一个字节的从标准输入(键盘)复制到标准输出(屏幕)。

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

int main()
{
    char c;
    
    while(read(STDIN_FILENO,&c,1)!=0)
        write(STDOUT_FILENO,&c,1);
    exit(0);
}

 敲一个字符,按下回车就显示

 
 

三、lseek

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

   lseek 定位一个文件,返回当前光标距文件开头的字节数

offset 偏移量
SEEK_SET 参数offset 即为新的读写位置.
SEEK_CUR 以目前的读写位置往后增加offset 个位移量.
SEEK_END

将读写位置指向文件尾后再增加offset 个位移量. 当whence 值为SEEK_CUR 或SEEK_END 时, 参数offet 允许负值的出现.

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

int main()
{
    /**
        abc.text文件里存放一个字符串“abcdefghijklmn”
    */
    int fd=open("abc.text",O_RDONLY,0);

    int buf[20];
    int num[10];
    int buf_size=read(fd,buf,20);
    printf("buf_size=%d",buf_size);

    //把光标设置到偏移量为2的地方
    int current_position=lseek(fd,2,SEEK_SET);
    printf("current_position=%d",current_position);

    //将光标后移3位
    int new_position_1=lseek(fd,3,SEEK_CUR);
    printf("new_position_1=%d",new_position_1);
    
    //读文件3个字节,输出到屏幕上
    read(fd,num,new_position_1-current_position);
    write(STDOUT_FILENO,num,new_position-current_position);
    printf("\n");

    //以文件末尾为基准,向前移1位
    int new_position_2 =lseek(fd,-1,SEEK_END);
    pritnf("new_position_2=%d",new_position_2);

    //把光标置于文件末尾,用这种办办法也可以算作求出文件大小
    int end_position=lseek(fd,0,SEEK_END);
    printf("end_position=%d",end_position);

}

来看一下运行结果,读字节的时候,可以发现文件的光标真的有在移动。

四、stat

        应用程序能够通过调用stat函数,检索到关于文件的信息,有时也称为文件的元数据

                     int  stat (const  char  *filename , struct  *buf );

结构体buf有13个成员,我们简单说两个常用的即可。st_size,表示文件字节数大小。st_mode成员则编码了文件访问许可位和访问类型。Linux再sys/stat.h中定义了宏谓词来确定st_mode成员的文件类型,

S_ISREG(m) 这是一个普通文件么?
S_ISDIR(m) 这是一个目录文件么

S_ISSOCK(m)

这是一个网络套接字么?

接下来看一个小代码,如何利用st_mode查看一个文件权限和类型 

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

int main()
{
    struct stat s;
    char *readok,*writeok,*exeok;

    stat(argv[1],&s);
    if(S_ISREG(s.st_mode))//普通文件
        type="regular";
    else if(S_ISDIR(s.st_mode))//目录
        type="directionary";
    else type="others";

    //查看读权限
    if((s.st_mode&S_IRUSR))
         readok="yes";
    else readok="no";
    
    //查看写权限
    if((s.st_mode&S_IWUSR))
         writeok="yes";
        else writeok="no";

    //查看执行权限
    if((s.st_mode&S_IXUSR))
         exeok="yes";
        else exeok="no";
    printf("type\treadok\twriteok\texeok\n");
    printf("%s\t%s\t\%s\t%s\n",type,readok,writeok,exeok);

}

五、共享文件 

        可以用许多不同的方式来共享Linux。内核用三个相关的树结构来表示打开的文件。

描述符表 每个进程都有它独立的描述符表,它的表项是由结成打开的文件描述符来索引的。每个进程都有它独立的描述符表项指向文件表中的一个表项
文件表 打开文件的集合是由一张文件表来表示的,所有进程共享这张表
v-node表 同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。

          ①下图展示了一个示例,其中描述符1和3通过不同给的打开文件表表项来引用两个不同的文件。这是一个典型的情况,没有共享文件,并且每个描述符对应一个不同的文件。

大致的代码
int fd1=open("abc.text",O_RDONLY,0);
int fd3=open("123.text",O_RDONLY,0);

           ② 多个描述符也可以通过不同的文件表表项来引用同一个文件。例如,如果同一个filename调用open函数两次,就会发生这种情况。关键思想是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据。

大致的代码
int fd1=open("abc.text",O_RDONLY,0);
int fd3=open("abc.text",O_RDONLY,0);

           ③父子进程共享相同的文件表集合,因此共享相同的文件位置。一个最重要的结果就是,在内核删除相应文件表表项之前,父子进程必须都关闭了他们的描述符。

          一些小练习

① 

//假设123.text文件中存放的内容是123456790
int fd1=open("abc.text",O_RDONLY,0);
int fd2=open("123.text",O_RDONLY,0);
int buf1[20],buf2[20];

read(fd1,buf1,20);
write(STDOUT_FILENO,buf1,20);

read(fd2,buf2,20);
write(STDOUT_FILENO,buf2,20);

他的结果是简单的。对应了第一种情况,打开了两个不同的文件,都是从头输出.。

② 

//假设我们读的文件中的内容是 abcdefgh

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char c1, c2, c3;
    char *fname = argv[1];
    fd1 = Open(fname, O_RDONLY, 0);
    fd2 = Open(fname, O_RDONLY, 0);
    fd3 = Open(fname, O_RDONLY, 0);
    //此处解释以下,dup2,简单来说这个函数的效果就是凡是对fd3的操作都可以看做是对fd2的操作
    dup2(fd2, fd3);

    Read(fd1, &c1, 1);
    Read(fd2, &c2, 1);
    Read(fd3, &c3, 1);
    printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);

    Close(fd1);
    Close(fd2);
    Close(fd3);
    return 0;
}

由于dup2,对fd3的操作都相当于对f2的操作。c1读到fd1中的a,c2读到了fd2中的a,此时fd2中的光标在‘a’的后面,所以c3读到了fd2中的b。

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char *fname = argv[1];

    //创建一个名为fname的新文件,可读可写,在里面写入pqrs
    fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
    Write(fd1, "pqrs", 4);	

    //以追加写的方式打开刚刚创建号的fname的文件,在里面写入jklmn
    fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
    Write(fd3, "jklmn", 5);

    //dup,使之后对fd2的操作都变为对fd1的操作
    fd2 = dup(fd1);  /* Allocates new descriptor */
    Write(fd2, "wxyz", 4);
    Write(fd3, "ef", 2);

    Close(fd1);
    Close(fd2);
    Close(fd3);
    return 0;
}

最终它的运行结果是 pqrswxyznef ,我们来看一下他的实现过程。

//假设读取的文件中存放的是abcdefgh

int main(int argc, char *argv[])
{
    int fd1;
    int s = getpid() & 0x1;
    printf("s=%d\n",s);

    char c1, c2;
    char *fname = argv[1];

    fd1 = open(fname, O_RDONLY, 0);
    read(fd1, &c1, 1);

    if (fork()) {
	    /* Parent */
	    sleep(s);
	    read(fd1, &c2, 1);
	    printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
    } else {
	    /* Child */
	    sleep(1-s);
	    read(fd1, &c2, 1);
	    printf("Child: c1 = %c, c2 = %c\n", c1, c2);
    }
    return 0;
}

可以看到,子进程打印结束后,大概等待一秒,父进程才打印出来。,read在fork之前进行了第一次读,c1没有争议的读到了a,此时光标在‘a’后。子进程因为没有sleep,从而先比父进程先读,读到了b,此时光标在‘b’后。父进程sleep结束后,读取c2,也就读到了c。

发布了22 篇原创文章 · 获赞 2 · 访问量 5437

猜你喜欢

转载自blog.csdn.net/qq_43135849/article/details/103313036