APUE学习笔记--文本IO(系统调用IO)(open、read、write、lseek、close、fileno、fdopen、truncate、ftruncate)

  • 前言

    • 当前笔记是基于我现在使用的centos6.3系统,各种细节最终以当前使用系统的man手册为准。
    • 文本IO的函数位于man手册第二部分,man 2 命令名 打开文本IO帮助文档。
  • stdio(标准IO)与文本IO(系统调用IO)的区别

    • 文本IO:无缓冲,响应速度快。
    • 标准IO:有缓冲,吞吐量大。
    • 尽可能使用标准IO。可移植性好,吞吐量大,提高系统整体效率。
    • 不要标准IO与文本IO混用。标准IO存在缓冲,两者混用,基本会出问题。
    • 例子如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{

	putchar('a');
	write(1,"b",1);//注意这里不能写成write(stdout,"b",1)。stdout是FILE指针类型;

	putchar('a');
	write(1,"b",1);
	exit(0);
}

输出结果为:aabb。
原因:putchar属于标准IO,它先存入缓冲区。

  • fileno

    int fileno(FILE *stream);
    返回stream对应的文件描述符。

  • fdopen

    FILE *fdopen(int fd, const char *mode);
    以mode模式打开fd,返回文件流。更多细节阅读man 3 fdopen。

  • 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);

      • open使用可变参数实现。
      • pathname:文件名
      • flags:(以下三种必选其一)
        • O_RDONLY:只读
        • O_WRONLY:只写
        • O_RDWR:读写
        • 文件创建标志与文件状态标志可与以上三种进行或运算,一起放在flag位上。
        • 文件创建标志:
          • O_CREAT:文件不存在,则创建文件
          • O_EXCL:
          • O_NOCTTY
          • O_TRUNC.:文件存在,截断文件,长度为0。文件不存在,则创建文件,长度为0.
        • 文件状态标志:(待补充)
      • mode(只有使用了O_CREAT时,mode才有意义。)
        • mode=00700,则代表创建的文件,user 用户的权限为rwx。
        • mode=00070,则代表创建的文件,group用户的权限为rwx。
        • 同理,可以得到更多的三类用户的权限组合。
        • 经过 (mode & ~umask)计算后,才得到创建的文件最终的权限。
    • 返回值

      执行成功时,返回文件描述符。否则返回-1.并设置errno值。

  • read

    • 功能描述:
      从文件描述符fd读取count个字节的数据存放入buf数组中。
    • 函数定义:
      #include <unistd.h>
      ssize_t read(int fd, void *buf, size_t count);
      • buf:存数据的缓存。count:读取的字节数量。fd:文件描述符。
      • 如果count==0,则read返回0,不造成其他影响。
      • 如果count>SSIZE_MAX,读取结果无法预知。
    • 返回值
      • 返回读取的字节个数,并把读取指针向前移动相应的数目。
      • 如果读取的字节个数小于它本想读取的个数,这是正常的。
      • 如果读取失败,则返回-1,并设置相应的errno值。此时,读取指针的位置无法预知。
  • write

    • 功能描述
      从buf指向的位置往后读取count字节,写入fd中。
    • 函数定义
      #include <unistd.h>
      ssize_t write(int fd, const void *buf, size_t count);
      • 写入fd中的数据个数可能小于count,这是正常的。
    • 返回值
      • 写入成功的话,返回写入的字节数。0代表写入0个字符。
      • 写入失败,返回-1,并设置相应的errno。
  • lseek

    • 功能描述

      重定向读写文件指针偏移量。

    • 函数定义

      #include <sys/types.h>
      #include <unistd.h>
      off_t lseek(int fd, off_t offset, int whence);

      • whence:
        • SEEK_SET:文件首部
        • SEEK_CUR:文件指针当前位置
        • SEEK_CUR:文件尾。
      • offset:偏移量
    • lseek允许offset+whence超过文件长度,这不会改变文件长度。如果在这个位置还写了数据,那在这个位置读数据时,会返回’\0’,直到文件长度扩展后,包括了之前的位置。

    • 返回值
      • 成功的话,返回现在的指针位置与文件头之间的偏移量。
      • 失败的话,返回-1,并设置errno值。
  • close

    • 函数定义

      #include <unistd.h>
      int close(int fd);
      释放该文件描述符。

    • 返回值

      成功返回0,失败返回-1,并设置errno值。

  • 使用open、read、write、close实现cp

/****************
这个程序并没有考虑中断等各种异常情况,等待之后学习,再完善。
********************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFFSIZE 1024 

int main(int agrc,char ** argv)
{
	int sfd,dfd;
	int len;
	char buff[BUFFSIZE];
	if(agrc !=3)
	{
		printf("Usage----%s <source file><dest file>",argv[0]);
		exit(EXIT_FAILURE);
	}
	
	sfd=open(argv[1],O_RDONLY);
	if(sfd<0)
	{
		perror("sfd open()");
		exit(EXIT_FAILURE);
	}
	dfd = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);
	if(dfd<0)
	{
		perror("dfd open()");
		close(sfd);
		exit(EXIT_FAILURE);
	}

	while(1)
	{
		len = read(sfd,buff,BUFFSIZE);
		if(len ==-1)
		{
			perror("read()");
			break;
		}
		if(len==0)
			break;
		write(dfd,buff,len);//可能一次没写入全部len个数据。等待之后完善。
	}
	close(sfd);
	close(dfd);
	exit(EXIT_FAILURE);
}

  • truncate、truncate

    • 函数定义

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

      int truncate(const char *path, off_t length);
      int ftruncate(int fd, off_t length);

    • 函数功能:

      截取文件的前length个字节,若文件长度不到length,则多余的部分填充’\0’。若长度超过length,则多余的部分,数据丢失。文件偏移不改变。

    • 注意事项
      • truncate path是文件地址,ftruncate的fd是已经打开的文件描述符。
      • truncate要求该文件可写;ftruncate要求该文件以可读的方式打开。
      • 执行成功,返回0;执行失败返回-1,且设置对应的errno。
  • 使用read、write、ftruncate等实现删除某文件的第10行

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

#define BUFFSIZE 1024

//初始化两个文件描述符
int init(int * _rfd,int * _wfd)
{
	
	*_rfd = open("text",O_RDONLY);
	if(*_rfd == -1)
	{
		perror("rfd open()");
		return -1;
	}
	*_wfd=open("text",O_RDWR);
	if(*_wfd == -1)
	{
		perror("wfd open()");
		close(*_rfd);
		return -1;
	}
	return 0;
}

int findLine(int * _line10Position,int * _line11Position,int _rfd)
{
	int len=0,			 //记录每次read读取的字节数
		count= 0,    //总共已经判别是否为'\n'的字节数
		tmp=0,    //记录每次read后,判别了多少个字符是否为'\n'
		flag=1,     //0--标志找到了第9和第10个‘\n’,也就是存在第十行
		countLine=1;//已经找到了几个'\n'     
	char buff[BUFFSIZE];
	
	while(flag)
	{
		tmp=0;
		len = read(_rfd,buff,BUFFSIZE);
		if(len==-1)
		{
			perror("read");
			break;
		}
		if(len==0)
			break;
		while(tmp<len)//寻找第9和第10个\n
		{
			count++;
			if(buff[tmp]=='\n')
			{
				countLine++;
				if(countLine==10)
				{
					*_line10Position=count;
				}
				if(countLine==11)
				{
					*_line11Position=count;
					flag=0;
					break;
				}
			}
			tmp++;
		}

	}
	return flag;
}


int truncate10Line(int _rfd,int _wfd,int  _line10Position,int  _line11Position)
{
		int len;
		char buff[BUFFSIZE];
	//	fprintf(stdout,"找到10行\n");
	//	printf("line10=%d,line11=%d\n",_line10Position,_line11Position);	

	//	ftruncate(_wfd,_line10Position);
		lseek(_wfd,_line10Position,SEEK_SET);
		lseek(_rfd,_line11Position,SEEK_SET);
	//	fprintf(stdout,"进入覆盖循环");
		while(1)
		{
			len=read(_rfd,buff,BUFFSIZE);
			fprintf(stdout,"len=%d\n",len);
			if(len == -1)
			{
				perror("read()");
				return -1;
			}
			if(len==0)
				break;
			_line10Position+=len;
		//	fprintf(stdout,"读取%d\n",len);
			write(_wfd,buff,len);
		}
		ftruncate(_wfd,_line10Position);
		
	return 1;
}



int main()
{
	int rfd,wfd;
	int line10Position,line11Position;
	
	if(init(&rfd,&wfd)==-1)//初始化两个文件描述符失败
		exit(EXIT_FAILURE);
	
	if(!findLine(&line10Position,&line11Position,rfd))//存在第十行
	{
		if(truncate10Line(rfd,wfd,line10Position,line11Position) == -1)//删除第十行失败
		{
			close(rfd);
			close(wfd);
			exit(EXIT_FAILURE);	
		}
					
	}
	else
		fprintf(stdout,"不存在第十行!\n");
	

	close(rfd);
	close(wfd);

	exit(EXIT_SUCCESS);
}	

  • dup、dup2重定向与原子操作

  • dup

    • 函数定义

      #include <unistd.h>

      int dup(int oldfd);
      int dup2(int oldfd, int newfd);

    • 函数功能
      • 建一个oldfd的副本。占用另一个文件描述符。
      • dup使用现在没在使用的最小的文件描述符。
      • dup2,这是原子操作,立马在newfd创建一个oldfd的副本。
        • 若oldfd不是一个有效的文件描述符,则执行失败,newfd也不会被关闭。
        • 如oldfd是一个有效的文件描述符,但newfd和oldfd的值相同,则dup2不作任何改变,返回newfd。
      • dup、dup2执行成功的话,新旧两个文件描述符可以互换使用。它们关联同一个文件描述符,同时共享文件偏移和文件状态标志(file status flag)。比如 ,其中一个使用lseek修改了文件偏移,另一个的文件偏移也会跟着修改。
      • 但是两者不会共享文件描述标志(file descriptor flags),The close-on-exec flag (FD_CLOEXEC; see fcntl(2)) for the duplicate descriptor
        is off.
    • 例子
      • 程序一程序功能:关闭默认的stdout流,再打开一个流,利用dup使puts重定向到文件中去。
      • 可能情况一:默认stdout没打开,close(1)可能出错。
      • 可能情况二:stdout打开的,而且也是1。在close(1)后,刚好有其他程序占用了文件描述符1,则这个函数打开的fd!=1。于是puts就输出到其他程序中去了。
      • 出问题的原因在于dup不是原子操作,且没考虑stdout打没打开的问题。
    • 鉴于以上,使用dup2实现,也就是下面的程序二。这是原子操作。

    • 由于我们这个程序把文件描述符1修改了。在大型项目中,会影响到别人的程序,因此还需要恢复现场。

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

#define FNAME "/tmp/out/text"
int main()
{
        int fd;
        close(1);
        fd = open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600);//关闭1后,新打开的会选择现在最小的文件描述符打开。
        if(fd==-1)
        {
          perror("open()");
          exit(EXIT_FAILURE);
        }

        puts("hello!");
        exit(EXIT_SUCCESS);
}  
//程序二
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FNAME "/tmp/out/text"
int main()
{
        int fd;
        fd = open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600);//关闭1后,新打开的会选择现在最小的文件描述符打开。
        if(fd==-1)
        {
          perror("open()");
          exit(EXIT_FAILURE);
        }
		if(dup2(fd,1) == -1)
		{
			perror("dup2()");
			exit(EXIT_FAILURE);
		}
		if(fd!=1)//dup2,oldfd与newfd相同时,返回newfd,且不做任何修改。
			close(fd);
        puts("hello!");
        exit(EXIT_SUCCESS);
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值