C语言中的I/O系统调用(Create, Open, Close, Read, Write)

重要术语

    什么是文件描述符?
    文件描述符是整数,用于唯一标识进程的打开文件。
    文件描述符表:文件描述符表是整数数组索引的集合,这些整数数组的索引是文件描述符,整数数组的元素是指向文件表条目的指针。操作系统为每个进程提供了一个唯一的文件描述符表。
    文件表条目:文件表条目是打开文件的内存代理结构,当进程请求打开文件时创建,同时这些条目记录文件位置。
在这里插入图片描述
    标准文件描述符:当进程启动时,该进程文件描述符表的fd 0、1、2将自动打开,默认情况下这3个fd中的每一个都引用名为/dev/tty的文件的文件表条目。
    /dev/tty:终端的内存代理
    终端:组合键盘/视频屏幕
在这里插入图片描述
    从stdin读取=>从fd 0读取:每当我们从键盘写入字符,都会通过fd0从stdin读取并保存到名为/dev/tty的文件中。
    写入stdout =>写入fd 1:我们看到视频屏幕的任何输出,都来自/dev/tty文件,并通过fd1写入屏幕的标准输出。
    写入stderr =>写入fd 2:我们看到的视频屏幕的任何错误,它也是从/dev/tty文件通过fd2写入stderr。

I/O系统调用

    基本上共有5种类型的I/O系统调用:

1. Create

    用于创建一个新的空文件。

C语言语法:
int creat(char *filename, mode_t mode)

    参数:

  • filename:要创建的文件的名称。
  • mode:指定新文件的权限。

    返回值:

  • 返回第一个未使用的文件描述符(通常在进程中首次使用creat时为3,因为保留了fd 0、1、2)
  • 错误时返回-1

    在OS中的运作方式

  • 在磁盘上创建新的空文件
  • 创建文件表条目
  • 将第一个未使用的文件描述符设置为指向文件表条目
  • 返回使用的文件描述符,失败时返回-1
2. open

    用于打开文件进行读取、写入或两者兼有。

C语言语法:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>  
int open (const char* Path, int flags [, int mode ]);

    参数:

  • Path:要使用的文件的路径
    • 所需文件不在当前目录时,请使用以“/”开头的绝对路径。
    • 所需文件在当前目录时,请使用相对路径
  • flags:O_RDONLY:只读,O_WRONLY:只写,O_RDWR:读写,O_CREAT:创建不存在的文件,O_EXCL:阻止创建已存在的文件

    在OS中的运作方式

  • 查找磁盘上的现有文件
  • 创建文件表条目
  • 将第一个未使用的文件描述符设置为指向文件表条目
  • 返回使用的文件描述符,失败时返回-1
// C program to illustrate
// open system call
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
extern int errno;
int main()
{	
	// if file does not have in directory
	// then file foo.txt is created.
	int fd = open("foo.txt", O_RDONLY | O_CREAT);
	
	printf("fd = %d\n", fd);
	
	if (fd ==-1)
	{
		// print which type of error have in a code
		printf("Error Number % d\n", errno);
		
		// print program detail "Success or failure"
		perror("Program");				
	}
	return 0;
}

    输出:

fd = 3
3. close

    告诉操作系统你已经使用完成了一个文件描述符并关闭fd所指向的文件。

C语言语法:
#include <fcntl.h>
int close(int fd);

    参数

  • fd:文件描述符

    返回值

  • 0 成功时返回
  • -1 错误时返回

    在OS中的运作方式

  • 销毁文件表条目,该条目被文件描述符表的元素fd引用
    (只要没有其他进程指向它!)
  • 将文件描述符表的元素fd设置为NULL
// C program to illustrate close system Call
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	int fd1 = open("foo.txt", O_RDONLY);
	if (fd1 < 0)
	{
		perror("c1");
		exit(1);
	}
	printf("opened the fd = % d\n", fd1);
	
	// Using close system Call
	if (close(fd1) < 0)
	{
		perror("c1");
		exit(1);
	}
	printf("closed the fd.\n");
}

    输出:

opened the fd = 3
closed the fd.
// C program to illustrate close system Call
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	// assume that foo.txt is already created
	int fd1 = open("foo.txt", O_RDONLY, 0);
	close(fd1);
	
	// assume that baz.tzt is already created
	int fd2 = open("baz.txt", O_RDONLY, 0);
	
	printf("fd2 = % d\n", fd2);
	exit(0);
}

    输出:

fd2 = 3

    在这里,在这段代码中第一个open()返回3。因为当创建主进程时,fd 0、1、2已经被stdin、stdout和stderr使用,所以第一个未使用的文件描述符是文件描述符表中的3。在之后的close()系统调用中,文件描述符3空闲了,所以将文件描述符3设置为null。所以当我们调用第二个open()时,第一个未使用的fd也是3。所以,这个程序的输出是3。

4. read

    从文件描述符fd指示的文件中,read()函数读取cnt字节,并将cnt字节写入buf指示的内存区域。成功执行的read()函数会更新文件的访问时间。

C语言语法
size_t read (int fd, void* buf, size_t cnt);

    参数

  • fd:文件描述符
  • buf:从中读取数据的buffer
  • cnt:buffer长度

    返回值:实际读取了多少字节

  • 成功时返回读取的字节数
  • 到达文件结尾时返回0
  • 错误时返回-1
  • 信号中断时返回-1

    要点

  • 为了避免溢出,buf需要指向长度不小于指定大小的有效内存地址。
  • fd应该是从open()返回的有效文件描述符,以执行读取操作,因为如果fd为NULL,则读取将产生错误。
  • cnt是请求读取的字节数,而返回值是实际读取的字节数。另外,有时read系统调用应该读取比cnt更少的字节。
// C program to illustrate
// read system Call
#include<stdio.h>
#include <fcntl.h>
int main()
{
int fd, sz;
char *c = (char *) calloc(100, sizeof(char));

fd = open("foo.txt", O_RDONLY);
if (fd < 0) { perror("r1"); exit(1); }

sz = read(fd, c, 10);
printf("called read(% d, c, 10). returned that"
		" %d bytes were read.\n", fd, sz);
c[sz] = '\0';
printf("Those bytes are as follows: % s\n", c);
}

    输出:

called read(3, c, 10).  returned that 10 bytes  were read.
Those bytes are as follows: 0 0 0 foo.

    假设foobar.txt文件由6个ASCII字符“foobar”组成。那么下面程序的输出是什么?

// C program to illustrate
// read system Call
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>

int main()
{
	char c;
	int fd1 = open("sample.txt", O_RDONLY, 0);
	int fd2 = open("sample.txt", O_RDONLY, 0);
	read(fd1, &c, 1);
	read(fd2, &c, 1);
	printf("c = %c\n", c);
	exit(0);
}

    输出:

c = f

    描述符fd1和fd2都有自己的打开文件表条目,因此每个描述符都有自己的foobar.txt文件位置。因此,从fd2读取foobar.txt文件的第一个字节,输出是c=f,而不是c=o。

5. write

    将cnt个字节从buf写入与fd关联的文件或套接字。cnt不应大于INT_MAX(在limits.h头文件中定义)。如果cnt为零,write()只返回0而不尝试任何其他操作。

#include <fcntl.h>
size_t write (int fd, void* buf, size_t cnt);

    参数

  • fd:文件描述符
  • buf:写入数据的buffer
  • cnt:buffer长度

    返回值

  • 返回成功写入的字节数
  • 到达文件结尾时返回0
  • 错误时返回-1
  • 信号中断时返回-1

    要点

  • 需要打开文件进行写入操作
  • buf至少需要与cnt一样长,因为如果buf的大小小于cnt,那么buf将导致溢出。
  • cnt是请求写入的字节数,而返回值是实际写入的字节数。当fd要写入的字节数少于cnt时,就会发生这种情况。
  • 如果write()被信号中断,则效果如下:
    -如果write()尚未写入任何数据,则返回-1并将errno设置为EINTR。
    -如果write()已成功写入某些数据,则返回中断前写入的字节数。
// C program to illustrate
// write system Call
#include<stdio.h>
#include <fcntl.h>
main()
{
int sz;

int fd = open("foo.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0)
{
	perror("r1");
	exit(1);
}

sz = write(fd, "hello geeks\n", strlen("hello geeks\n"));

printf("called write(% d, \"hello geeks\\n\", %d)."
	" It returned %d\n", fd, strlen("hello geeks\n"), sz);

close(fd);
}

    输出:

called write(3, "hello geeks\n", 12).  it returned 11

    在这里,当你在文件里看到foo.txt文件在运行代码之后,你会得到一个“hello geeks”。如果foo.txt文件文件已经有一些内容,然后write系统调用覆盖内容,所有以前的内容被删除,只有“hello geeks”的内容将在文件中。
    从程序中打印“hello world”,而不使用任何printf或cout函数。

// C program to illustrate
// I/O system Calls
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>

int main (void)
{
	int fd[2];
	char buf1[12] = "hello world";
	char buf2[12];

	// assume foobar.txt is already created
	fd[0] = open("foobar.txt", O_RDWR);		
	fd[1] = open("foobar.txt", O_RDWR);
	
	write(fd[0], buf1, strlen(buf1));		
	write(1, buf2, read(fd[1], buf2, 12));

	close(fd[0]);
	close(fd[1]);

	return 0;
}

    输出:

hello world

    在这段代码中,首先将buf1数组的字符串“hello world”写入标准输入fd[0],然后将该字符串写入标准输入buf2数组。之后,将buf2数组写入标准输出并打印输出“helloworld”。

参考文档

[1]Kadam Patel.Input-output system calls in C | Create, Open, Close, Read, Write[EB/OL].https://www.geeksforgeeks.org/input-output-system-calls-c-create-open-close-read-write/,2020-08-23.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值