关于Linux I/O的相关程序

相关知识

Linux中是号称一切皆文件的,我们对文件的操作主要有打开,关闭,读,写操作。

  • 打开 int open(char *filename , int flags ,mode_t mode)
    open函数将filename转化为一个文件描述符,并且返回文件描述符数字,返回的文件描述符数字是进程中没有打开的最小的描述符。
    flags指定以什么方式打开

    • O_RDONLY: 只读
    • O_WRONLY: 只写
    • O_RDWR: 可读可写
      还有其它一些方式:
    • O_CREAT:如果文件不存在,就创建一个截断(空)的文件
    • O_TRUNC:如果文件已经存在,就截断它
    • O_APPEND:在文件末尾添加
      mode参数指定了文件的访问权限(该参数只有在创建文件才有作用),如:S_IRUSR 使用者能够读这个文件。
  • 关闭 int close(int fd) 成功返回0,出错返回-1
    注意:关闭一个已经关闭的描述符会出错

  • ssize_t read(int fd,void *buf,size_t n)
    成功则返回读的字节数,若EOF则为0,出错则为-1,从文件描述符为fd的文件读取n个字节到内存buf处。

  • ssize_t write(int fd,const void *buf,size_t n)
    成功则为写的字节数,出错则为-1。从内存位置buf复制至多n个字节到文件描述符fd的当前文件位置。

再看看文件描述符表,文件表,v-node表是什么。

  • 文件描述符表 每个进程有自己的描述符表,每个表项是由进程中的打开的文件描述符来索引,意思就是,每个进程打开的文件都可以在这边找到一个对应的文件描述符,每个打开的文件描述符指向文件表中的一个表项。
  • 文件表 文件表是由打开的文件集合组成的,所有进程共享这张表,每个表项组成包括当前的文件位置,引用计数(当前指向该表项的描述符表项数),关闭一个描述符会减少相应的文件表表项中的引用计数,内核直到引用计数为0,才会删除这个文件表表项。还有一个指向v-node表中对应文件的指针。
  • v-node 所以进程共享,包含stat结构中的打大多数信息,stat结构包括文件的描述信息,比如文件大小,文件类型等。

第一个程序

#include "csapp.h"
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(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;
}/*abcde.txt,文件中为abcde。*/

首先,看这个程序如何运行,因为这个程序自己写了头文件,运用gcc file1.c csapp.h csapp.c -lpthread -o file1 就可以正常编译运行了。
这段程序为在文件中读3个字符,最关键的就是dup2函数怎么理解。
int dup2(int oldfd,int newfd)成功则为非负的描述符,出错为-1。
复制文件描述符表表项oldfd到描述符表项newfd。

程序分析

  • 首先字符指针为第二个参数,即文件名,三个open函数以只读方式打开同一个文件,每个文件表表项是独立的。
  • dup2函数将fd2复制到fd3,即在fd3中读就是在fd2中读,c1读了第一个字符a,c2读了第一个字符a,第三个read函数本来是在读fd3指的文件表项(即本来也应该读a),但是前面用dup2函数,就改为读fd2了,因为fd2已经读到了第二个字符,所以c3读的是b。

第二个程序

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char *fname = argv[1];
    fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
    Write(fd1, "pqrs", 4);	
    fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
    Write(fd3, "jklmn", 5);
    fd2 = dup(fd1);  /* Allocates new descriptor */
    Write(fd2, "wxyz", 4);
    Write(fd3, "ef", 2);

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

问在文件中写的内容是什么。

程序分析

  • 打开第二个参数指定的文件,返回的描述符给fd1,方式为如果文件不存在就创建一个空文件,存在就清空,以可读可写的方式打开,模式为所有者可读可写。
  • 向fd1所指的文件中写了“pqrs”,此时文件里的内容是pqrs,又以追加方式打开了同一个文件,返回的描述符给fd3,在fd3所指的文件后面写了“jklmn”,此时文件里的内容是pqrsjklmn,然后用了dup函数,这个函数复制文件描述符fd1给返回的文件描述符fd2,此时fd2所指的文件的光标是在第五个字符,然后向fd2所指的文件中写了“wxyz”,这4个字符覆盖了前面所写的“jklm”,此时文件里的内容是pqrswxyzn,然后在fd3所指的文件后面追写了“ef”。所以答案就是pqrswxyznef。

第三个程序

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1;
    int s = getpid() & 0x1;
    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;
}/*读的是abcde.txt,其中内容为abcde

程序分析

  • getpid()函数是获得进程号,再与0x1作位与运算,注意&和&&是不一样的,其实getpid()&0x1的结果就取决于getpid()的二进制的最低位是什么,如果是0,则最终结果是0,是1,最终结果是1。

  • 打开所给文件,从其中读一个字符放到c1中,即读了字符“a”,文件位置改为第二个字符处。然后fork()。这时产生一个子进程,子进程会有一个父进程描述符表的副本,但父子进程共享相同的打开文件集合,因此共享相同的文件位置。

  • 父子进程中都执行了sleep函数,一个进程不休眠,则另一个进程休眠1秒。所以输出结果是先执行父进程还是执行子进程,是由父进程进程号的二进制最低位决定的。

  • 现在假设先执行父进程,向文件中读一个字符,这时的文件位置已经到了第二个字符处了,所以父进程中的c2读了字符“b”,输出语句。

  • 然后执行子进程,从文件中读取一个字符,因为父子进程共享相同的打开文件集合,故此时的文件位置为第三个位置处,即子进程中的c2读了字符“c”,输出结果。

运行截图
在这里插入图片描述

第四个程序

/* $begin cpstdin */
#include "csapp.h"

int main(void) 
{
    char c;

    while(Read(STDIN_FILENO, &c, 1) != 0) 
	Write(STDOUT_FILENO, &c, 1);
    exit(0);
}

  • 这个程序的循环终止条件时read函数返回值=0,即碰到EOF时,所以这个程序要想终止,就得碰到EOF,可知在键盘上按Ctrl+D就是EOF,则程序就终止了。
  • 程序里面有两个宏定义STDIN_FILENO和STDOUT_FILENO,一个是标准输入,一个是标准输出。文件描述符就是0和1,另外2表示的是文件错误。

第五个程序

#include <stdio.h>
#include <stdlib.h>
int main()
{
    printf("h");
    printf("e");
    printf("l");
    printf("l");
    printf("o");
    printf("\n");
    fflush(stdout);
    exit(0);
}

这个程序是用来了解缓冲区的知识的。 以上输出为hello,并且换行。
现在考虑以下措施

  • 删掉 fflush(stdout),这时程序输出hello并且换行
  • 删掉printf("\n"),发现输出hello,不换行,fflush(stdout)是手动刷新缓冲区,将缓冲区里的内容输出。
  • 删掉 fflush(stdout)和printf("\n"),发现程序也是输出hello,不换行,这是因为程序终止,将缓冲区里的内容输出。
  • 现在删掉 fflush(stdout)和printf("\n"),将exit(0)改为_exit(0),发现不输出hello了,因为_exit(0)是退出时不会清除缓冲区,也就不会将hello输出了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值