系统级IO

系统级IO

csapp.c文件中包含大量封装函数,已对-1的返回情况做了处理,以下调用的函数有的用大写表示(已处理)。见第八章。

程序运行加-lpthread(多线程),这里考虑到csapp.h

Unix I/O
所有的I/O设备(如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写执行)
open,close,read,write,lseek。

  • 改变当前文件的位置。每个打开的文件内核保持一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。执行seek操作能将文件的当前位置设置为k。

共享文件

  • 描述符表。一个进程一个描述符表。描述表的表项是由进程打开的文件描述符索引的。每个打开的文件描述符表项指向文件表中的一个表项。
  • 文件表。进程共享。打开文件的集合是由文件表来表示。文件表的表项组成包括当前的文件位置、引用计数(refcnt)即指向该表项的描述符表项数、一个指向v-node表中对应表象的指针。
  • v-node表。进程共享。包含stat结构的大多数信息包括st_mode和st_size成员。

I/O重定向
原本Linux认为标准输出是显示屏,现在输出到文件。
dup2函数复制描述符表项oldfd到newfd,覆盖newfd以前的内容。若newfd已打开,会在复制oldfd前关闭newfd。

int dup2(int oldfs, int newfd);

返回:成功则为fd,出错为-1


打开和关闭文件:

  • 打开文件。一个应用程序要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数叫描述符。内核记录有关这个打开文件的所有信息。
    Linux shell创建的每个进程开始时都有3个打开的文件:标准输入(fd=0)、标准输出(fd=1)和标准错误(fd=2)。
int  open(char *filename, int flags, mode_t mode);

       open函数将filename转换为一个文件描述符,并返回描述符数字。flags参数指明了子进程的访问方式,也可以是一个或更多位掩码的或;mode参数指定了新文件的访问权限位。
       返回:成功为新文件描述符(最小的,但>=3),出错为-1。

  • 关闭文件。内核释放文件打开是创建的数据结构,并将这个描述符恢复到可用的描述符池。
 int close(int fd);

读和写文件
size_t被定义为unsigned long。size_t被定义为long。

  • 读文件。从文件复制n>0个字节到内存,从k开始,然后k变为k+n。若文件字节大小为m,k>=m时执行读操作会触发EOF的条件。
    read函数从描述符为fd的当前文件位置复制最多n个字节最多n个字节到内存位置buf。
ssize_t read(int fd, void *buf, size_t n);

       返回:成功则为读的字节数,读到文件末尾(EOF)返回0,出错返回-1。

  • 写文件。从内存复制n>0个字节到一个文件,从当前位置k开始,然后更新k。
    从内存buf复制至多n个字节到描述符fd的当前文件位置。
ssize_t write(int fd, const void *buf, size_t n);

       返回:成功则为写的字节数,出错则为-1。


/*abcde.txt*/
csapp

ffiles1:

#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文件打开了3次,然后重定向时dup2函数将描述符表项fd2复制到fd3,覆盖fd3的内容。
       每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据(联系读写文件时的k值变化)。在fd1,fd2指向的文件中分别读入一个字节后,k值为1,因此这里c1与c2值都为第一个字节c。
       dup2执行之后fd3原来指向的文件已关闭,现在指向的是fd2所指向的文件,fd3文件表和v-node表表项被删除。从此以后任何标准输出的数据都被重定向到fd2指向的文件。因此fd3在fd2指向的文件中继续读数据,此时再读一个字节,k值为2,c3值为第二个字节s。


ffiles2:

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1;
    int s = getpid() & 0x1;
	printf("pid = %d\n",getpid());
	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;
}

int s = getpid() & 0x1;
由于getpid的返回值不确定,这里会将s值设置为1或0。
若设置为1,Child(sleep(0))将会快于Parent(sleep(1));
若设置为0,Parent(sleep(0))将会快于Child(sleep(1));
在这里插入图片描述
在这里插入图片描述
       现在解释s设置为1时,Child和Parent的输出情况。
       首先,在fork还未创建子进程时,以只读的方式将abcde.txt文件打开了1次,返回fd1,然后读入一个字节到c1,此时文件的当前位置k=1,子进程中sleep(0)相当于没有等待,而子进程相当于有一个父进程描述符表的副本,父子进程共享相同的打开文件表集合,因此共享相同的位置,所以子进程读文件是也是从k=1开始读的,然后读入一个字节到c2,此时k=2,输出Child:c1=c,c2=s
       之后在sleep(1)后父进程开始read,父进程依旧是从fd1中读入一个字节,而打开文件表是所有进程共享的,之前子进程读了一个字节,所以父进程是从k=2开始读,然后再读入一个字节到c2,此时k=3,输出Parent:c1=c,c2=a

       s设置为0的情况同理。


ffiles3:

#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;
}

在这里插入图片描述
dup函数:int dup(int oldfd);
dup用来复制参数oldfd所指的文件描述符。当复制成功是,返回最小的尚未被使用过的文件描述符,若有错误则返回-1。返回的新文件描述符和参数oldfd指向同一个文件,这两个描述符共享同一个数据结构,共享所有的锁定,读写指针和各项全现或标志位。

       以可读可写的方式打开(O_RDWR;若文件不存在就创建一个截断的文件(O_CREAT);若存在就截断它,即清空文件(O_TRUNC)。这里命令行的第二个参数为文件abcde.txt,因此该文件打开时会被清空,返回fd1,然后从pqrs字符串复制4个字节到abcde.txt。
       以只写权限打开文件(O_WDONLY),在每次操作前设置文件的位置到文件的末尾(O_APPEND)。因此abcde.txt文件是以只写方式打开的,返回fd3,打开时已在文件的末尾,即 k = 4,(没有注明O_APPEND则这里还是 k = 0),再继续从jklmn字符串复制5个字节到abcde.txt,此时文件里的数据应是pqrsjklmn,k = 9。
       由dup函数的功能知fd2会指向fd1所指向的文件abcde.txt,拥有同一张文件表,然后从wxyz字符串复制4个字节到abcde.txt,但是由于之前fd1执行写操作后 k = 4,这时候fd2继续写的时候是从当前位置 k = 4 开始的,wxyz会覆盖原来的jklm,此时文件的数据应是pqrswxyzn。
       接着将ef复制到fd3的当前位置,从 k = 9开始写入,此时文件里的数据应是pqrsjklmnef,k = 11。


cpstdin:

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

int main(void) 
{
    char c;

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

在这里插入图片描述
STDIN_FILENO表示从键盘接收,是(fd)0的宏定义;STDOUT_FILENO表示从显示屏输出,是(fd)1的宏定义;当一个字符一个字符的输入csapp后,输入回车符就会再从缓冲区输出一个csapp。但并不会跳出循环。
Read(STDIN_FILENO, &c, 1)相当于C语言中的getchar();


statcheck:

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

int main (int argc, char **argv) 
{
    struct stat stat;
    char *type, *readok, *writeok, *executeok;

    /* $end statcheck */
    if (argc != 2) {
	fprintf(stderr, "usage: %s <filename>\n", argv[0]);
	exit(0);
    }
    /* $begin statcheck */
    Stat(argv[1], &stat);
    if (S_ISREG(stat.st_mode))     /* Determine file type */
	type = "regular";
    else if (S_ISDIR(stat.st_mode))
	type = "directory";
    else 
	type = "other";
    if ((stat.st_mode & S_IRUSR)) /* Check read access */
	readok = "r_yes";
    else
	readok = "r_no";
	if ((stat.st_mode & S_IWUSR)) /* Check write access */
	writeok = "w_yes";
    else
	writeok = "w_no";
	if ((stat.st_mode & S_IXUSR)) /* Check execute access */
	executeok = "e_yes";
    else
	executeok = "e_no";

    printf("type: %s, read: %s\t%s\t%s\n", type, readok, writeok, executeok);
    exit(0);
}


在执行ffiles的操作后,检查abcde.txt的信息(state数据结构中st_mode)。
在这里插入图片描述
这里注意当打开一个文件时,对包含该文件的每一个目录,都应该具有执行权限。而ffiles3程序中我们能知道这个文件具有S_IRUSER和S_IWUSER,经statcheck程序检验知道,它的确具有user的读写执行权限。

下面再看一个例子:
createmode:

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1;
    fd1 = Open("foo.txt", O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR);

    Close(fd1);
    return 0;
}

在这里插入图片描述
创建foo.txt时并没有给它用户的写权限,所以这里显示w_no。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值