Linux期末复习——文件I/O编程

Linux系统调用以及用户编程接口

三者关系

        系统调用、API以及系统命令之间关系:

什么是文件描述符?

        是一个非负整数,索引值

        打开或者创建一个文件的时候,内核会向进程返回一个文件描述符

        读写文件时,会向函数传递一个文件描述符 

底层文件I/O操作

        特点:不带缓存,直接对文件(设备)进行读写操作

基本文件操作(五个函数)

        open():打开或者创建文件,可以指定文件属性以及用户权限

        

int open(文件名,打开方式,权限)
第一个参数被打开文件名,可以包括路径
第二个参数
O_RDONLY只读方式打开
O_WRONLY只写方式打开
O_RDWR 读写方式打开
O_CREAT不存在就创建
O_NOCTTY如果文件为中断,这个中断不会调用open()的那个进程的控制中断

第三个参数
被打开文件权限S_I(R/W/X)(USR/GRP/OTH)
(读/写/运行)(用户/组/其他人)
返回值

成功:返回文件描述符

失败:-1

        close():关闭一个被打开的文件,成功返回0,失败返回-1

close(文件描述符)
唯一参数fd:文件描述符
返回值

0:成功

-1:出错

        read():把指定的文件描述符中读出的数据放入缓存区

 read(文件描述符,读的地方,读多少)
第一个参数文件描述符
第二个参数指定读的缓存在哪
第三个参数指定读的字节数
返回值

成功:已读字节数

失败:-1

到达文件尾部:0

        write():向打开的文件写数据,从当前指针位置开始。

 write(文件描述符,写的地方,写多少)
第一个参数文件描述符
第二个参数指定写的缓存在哪
第三个参数指定写的字节数
返回值

成功:已写字节数

失败:-1

        lseek():将文件指针定位到相应位置

        lseek(要定位文件的描述符,往那边便宜并且移动多少,从哪开始偏移)
第一个参数文件描述符
第二个参数偏移量,+向前移动,-向后移动
第三个参数
SEEK_SET当前位置是文件开头,新位置是偏移量
SEEK_CUR当前位置是文件指针位置,新位置是当前位置+偏移量
SEEK_END当前位置是文件结尾,新位置是文件大小+偏移量
返回值

成功:文件的当前位移

失败:-1

示例代码: 

/* copy_file.c */

/*头文件部分*/
#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdlib.h>

#include <stdio.h>

/*宏定义*/

#define	BUFFER_SIZE	1024		/* 每次读写缓存大小,影响运行效率,也就是都多少写多少*/

#define SRC_FILE_NAME	"src_file"	/* 源文件名 */

#define DEST_FILE_NAME	"dest_file"	/* 目标文件名文件名 */

#define OFFSET		10240		/* 复制的数据大小,用来算偏移多少 */

 	

int main()

{
    /*首先定义一些整形变量和数组*/
	int src_file, dest_file;/*整形变量用来存放文件描述符*/

	unsigned char buff[BUFFER_SIZE];/*无符号字符数组,用来存储源文件src_file内容*/	

    int real_read_len;	/*用来存储read返回的读到的字节数*/


	/* 以只读方式打开源文件 ,为了之后读取文件,返回的文件描述符赋值给src_file*/
    /*这里文件名字是宏定义*/
	src_file = open(SRC_FILE_NAME, O_RDONLY);

	

	/* 以只写方式打开目标文件,若此文件不存在则创建, 访问权限值为644,即user:读写、group:读、others:读权限,三位八进制数是664,返回的文件描述符赋值给dest_file*/
    /*这里文件名字是宏定义*/
	dest_file = open(DEST_FILE_NAME, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);

	/*判断文件描述符是什么,如果open函数返回-1表示打开失败*/
	if (src_file < 0 || dest_file < 0)

	{

		printf("Open file error\n");

		exit(1);

	}	


	/* 将源文件的读写指针移到最后10KB的起始位置*/
    /*SEEK_END将起点设置为文件的结尾,向前偏移10K字节,定为当前文件读写位置*/
    /*如果是SEEK_SET起点就是文件开头,SEEK_CUR起点就是当前指针位置*/
	lseek(src_file, -OFFSET, SEEK_END);

	/* 读取源文件src_file的数据,放在数组buff中,读取的量是sizeof(buff)字节即1024字节*/
/*最后10KB数据并写到目标文件中,每次读写1KB */

    /*当read正常返回独到的字节数的时候*/
	while ((real_read_len = read(src_file, buff, sizeof(buff))) > 0)

	{                     
        /*写入dest_file文件,从buff字符数组,写real_read_len个字节数据*/
		write(dest_file, buff, real_read_len);     
	}

	
    /*最后关闭两个文件*/
	close(dest_file);

	close(src_file);

	
	return 0;

}

  文件锁

        如果文件已经共享,如何让多个用户共同操作一个文件:给文件上锁,避免共享的资源产生竞争状态。 fcntl()函数就是实现的方法

        fcntl()函数:不仅可以施加建议锁,还能上强制锁,还能对文件某一记录上记录锁

        记录锁分为读取锁(共享锁)和写入锁(排斥锁)

        

fcntl(上锁文件的描述符,具体的操作,&lock)函数语法要点
第一个参数文件描述符
第二个参数
F_GETLK根据lock参数,决定上不上锁
F_SETFL设置lock参数值的文件锁
F_SETLKW无法获取锁就进入睡眠

第三个参数lock:记录锁的具体状态

lock结构体1_type变量取值
F_RDLCK读取锁
F_WRLCK写入锁
F_UNLCK解锁

                记录锁源码:

/* lock_set.c 该函数作用:给fd文件描述符所对应的文件上type类型的锁(包括:读锁、写锁或解锁) */

/*测试代码中通过访问lock_set方法来上锁解锁*/
int lock_set(int fd, int type)

{

	struct flock old_lock, lock;/*创建lock结构图*/

	lock.l_whence = SEEK_SET;/* 文件读写指针起始位置在文件开头 */

	lock.l_start = 0;        /*相对位移量;加锁整个文件*/

	lock.l_len = 0;          /*加锁区域长度;加锁整个文件*/

	lock.l_type = type;      /*type包含:读锁、写锁和解锁三个类型;给唯一需要赋值的锁类型l_type赋值文件锁,这里是关键*/

	lock.l_pid = -1;         /*该文件拥有文件锁的进程号,默认为-1*/	

	
    /* 首先需要执行第一次fcntl()函数判断文件是否可以上锁,因为只有文件为解锁(不上锁)状态才能上锁 */
    /*F_GETLK:根据lock参数值,决定是否上锁,get lock*/
    /*&lock:取flock结构体变量lock的首地址*/
	fcntl(fd, F_GETLK, &lock);

                                  

                                    

        /*此处的fcntl()函数只是为了测试fd是否能上锁,因为只有文件为解锁(不上锁)状态才能上锁;如果文件已经上锁,

          根据F_GETLK参数就要看情况判断:

          如果之前已经存在的是读锁则还可以上读锁,如果是写锁则不能上读锁或写锁;

          也就是说:要给一个没上锁的文件上锁要执行两次fcntl()函数;

          第一次执行:F_GETLK参数将文件设置为解锁状态;fcntl()函数返回-1,表达的含义经过测试该文件可以上锁;

          第二次fcntl()函数执行就可以放心大胆的上锁,fcntl()函数会返回0,上锁成功*/

 



	/*如果lock.l_type不是解锁状态,意味着已经存在其他锁*/

	if (lock.l_type != F_UNLCK)

	{

		/* 判断文件不能上锁的原因 */

		if (lock.l_type == F_RDLCK) /* 该文件已有读锁 */

		{

			printf("Read lock already set by process PID:%d\n", lock.l_pid);

		}

		else if (lock.l_type == F_WRLCK) /* 该文件已有写锁 */

		{

			printf("Write lock already set by process PID:%d\n", lock.l_pid);

		}			

	}



       	

	/* l_type 可能已被F_GETLK修改过,所以需要重新赋值*/
	lock.l_type = type;

	


/* 根据不同的type值,第二次执行fcntl函数,进行阻塞式上锁或解锁,所谓阻塞式:意味着如果没有成功上锁或解锁或捕捉到信号,则程序一直处于阻塞状态 */

/*F_SETLKW:在无法获取锁时,进入睡眠,set lock  wait;如果可获得锁或捕捉到信号,则返回*/

/*&lock:取flock结构体变量lock的首地址*/

	if ((fcntl(fd, F_SETLKW, &lock)) < 0) 

    /*此处fcntl()函数作用:给fd文件描述符所对应的文件上lock结构体变量所确定的l_type类型的锁;
      此时用F_GETLKW直接给文件上文件锁,因为第一次fcntl()函数已经测试过
      该文件可以上锁,于是本次fcntl()函数可以成功执行,并返回0 */




	{

		printf("Lock failed:type = %d\n", lock.l_type);

		return 1;

	}

		

	switch(lock.l_type)

	{

		case F_RDLCK:

		{

			printf("Read lock set by process PID:%d\n", getpid());/*该文件已被getpid()进程上了读锁*/

		}

		break;



		case F_WRLCK:

		{

			printf("Write lock set by process PID:%d\n", getpid());/*该文件已被getpid()进程上了写锁*/

		}

		break;



		case F_UNLCK:

		{

			printf("Release lock by process PID:%d\n", getpid());/*该文件已被getpid()进程解锁*/

			return 1;

		}

		break;



		default:

		break;

	}

	

	return 0;

}

读取锁测试:

/* read_lock.c */

#include <unistd.h>

#include <sys/file.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <stdio.h>

#include <stdlib.h>

#include "lock_set.c"


int main(void)

{

	int fd;/*整形变量fd存放文件标识符*/

	
    /*读写方式打开,不存在就创建,权限0644*/
	fd = open("hello",O_RDWR | O_CREAT, 0644);

	if(fd < 0)

	{

		printf("Open file error\n");

		exit(1);

	}	

	

	lock_set(fd, F_RDLCK);/* 给文件上读取锁,上锁后程序处于等待键盘输入字符的等待状态; */



	getchar();/*取键盘键入的字符,可以让程序继续执行下去*/	

	

	lock_set(fd, F_UNLCK);/* 给文件解锁 */



	getchar();

	

	close(fd);

	exit(0);

}

写入锁测试:

/* write_lock.c */

#include <unistd.h>

#include <sys/file.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <stdio.h>

#include <stdlib.h>

#include "lock_set.c"


int main(void)

{

	int fd;	

	

	fd = open("hello",O_RDWR | O_CREAT, 0644);/* 读写打开文件 */

	if(fd < 0)

	{

		printf("Open file error\n");

		exit(1);

	}	

	

	lock_set(fd, F_WRLCK);/* 给文件上写入锁 */

	getchar();	

	

	lock_set(fd, F_UNLCK);/* 给文件解锁 */

	getchar();

	

	close(fd);

	

	exit(0);

}

多路复用

        处理I/O操作可能会被阻塞的情况

        select()函数有什么用:

  • 提高性能:select能够帮助程序在处理多个输入/输出源时更加高效。通过使用select,可以使得程序在等待一个I/O操作完成时继续执行其他任务,从而提高整体性能。
  • 可扩展性:select使得程序可以处理更多的并发连接。这对于开发服务器应用程序尤为重要,因为它们需要同时处理多个客户端连接。
  • 跨平台兼容性:select是一个通用的I/O复用技术,它在不同的操作系统和平台上都有实现。这意味着使用select编写的代码具有较好的可移植性。
  • 建立基础知识:掌握select这类基本的I/O复用技术,有助于理解更高级和更复杂的技术,例如poll和epoll等。

摘取自:https://blog.csdn.net/qq_21438461/article/details/130143669
 

以下源码演示一个调用select(函数)监听三个终端(两个管道文件虚拟终端,一个主程序虚拟终端)的输入,并且进行相应的处理。在主程序的虚拟终端中输入q/Q实现程序结束,在管道终端数据输入在主程序终端中显示。

/* multiplex_select.c */

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>

/*宏定义*/
#define MAX_BUFFER_SIZE		1024			/* 缓冲区大小*/
#define IN_FILES		3			/* 多路复用时输入文件数目*/
#define TIME_DELAY		600			/* 超时时间秒数 */
#define MAX(a, b)		((a > b)?(a):(b))

int main(void)
{
	int fds[IN_FILES];/*fds[0],fds[1],fds[2]*/
	char buf[MAX_BUFFER_SIZE];
	int i, res, real_read, maxfd;
	struct timeval tv;
	fd_set inset,tmp_inset;/*文件描述符集fd_set,用来保存由select函数监控的文件所对应的文件描述符*/	
	

	fds[0] = 0;

	/*首先按一定的权限打开两个管道源文件*/
    /*按照只读|非阻塞方式打开管道文件in1,非阻塞就是不能等待立即返回结果的含义*/
	if((fds[1] = open ("in1", O_RDONLY|O_NONBLOCK)) < 0)
        
	{
		printf("Open in1 error\n");
		return 1;
	}
  		    
 	if((fds[2] = open ("in2", O_RDONLY|O_NONBLOCK)) < 0)
 	{
 		printf("Open in2 error\n");
		return 1;
	}

	/*取出两个文件描述符中的较大者*/
  	maxfd = MAX(MAX(fds[0], fds[1]), fds[2]);  	

  	/*初始化文件描述符集inset,并在该集合中加入相应的三个文件描述符:标准输入,in1和in2的文件描述符*/
  	FD_ZERO(&inset); 
  	for (i = 0; i < IN_FILES; i++)
  	{
  		FD_SET(fds[i], &inset);
  	}

/*fd_set是一组文件描述字(fd)的集合,它用一位来表示一个fd*/
 
/*过去,一个fd_set通常只能包含小于32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件<sys/select.h>中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,*/ 

/*linux shell 输入如下命令就知道,其中的“-n: file descriptors”就是linux系统允许的文件描述符的最大限制值。
# ulimit -a
-f: file size (blocks) unlimited
-t: cpu time (seconds) unlimited
-d: data seg size (kb) unlimited
-s: stack size (kb) 8192
-c: core file size (blocks) 0
-m: resident set size (kb) unlimited
-l: locked memory (kb) 64
-p: processes 128
-n: file descriptors 1024*/ 

/*FD_SET(0, &set); 将set的0号位置1,如set原来是00000000,则现在变为10000000(对应10进制就是1,2进制转10进制从左往右算),这样fd==0文件描述字就被加进set */ 
   
/*FD_CLR(4, &set); 将set的4号位置0,如set原来是10001000,则现在变为10000000,这样fd==4的文件描述字就被从set中清除了 */  
   
/*FD_ISSET(5, &set); 测试set的5号位是否为1,如果set原来是10000100(对应10进制就是33),则返回非零,表明fd==5的文件描述字在set中;否则返回0*/


  	FD_SET(0, &inset);

  	tv.tv_sec = TIME_DELAY;
  	tv.tv_usec = 0;
  	
  	/*循环测试该文件描述符是否准备就绪,并调用select函数对相关文件描述符做对应操作*/
  	while(FD_ISSET(fds[0],&inset) || FD_ISSET(fds[1],&inset) || FD_ISSET(fds[2], &inset))
  	{ 
        /*文件描述符集合的备份,避免每次进行初始化*/
  		tmp_inset = inset;

  		res = select(maxfd + 1, &tmp_inset, NULL, NULL, &tv);
        /*测试写文件描述符集;
          res = select(maxfd + 1, NULL,&tmp_inset , NULL, &tv);*/

 
                /*maxfd+1:需要监视的文件描述符最大值+1,减少监控二进制位数,提高效率*/
                /* &tmp_inset:由select()监视的读文件描述符集合*/
                /*NULL:由select()监视的写文件描述符集合*/
                /*NULL:由select()监视的异常出错文件描述符集合*/
                /*&tv:若等待了tv时间还没有检测到任何文件描述符准备好,则立即返回*/
              
                /*select()执行成功则返回准备好的文件描述符的数目,如果返回-1代表出错
                该函数执行过后只有数据输入的文件描述符才能继续存在tmp_inset文件描述符集当中,没有输入的则清除出tmp_inset*/
  		
  		switch(res)
  		{
  			case -1:
  			{
  				printf("Select error\n");
  				return 1;
  			}
  			break;
  			
  			case 0: /* Timeout */
  			{
  				printf("Time out\n");
  				return 1;
  			}  			
  			break;
  			
  			default:/*if res非0、非-1,即存在已经准备好的文件描述符,也就是说有某个管道文件或标准输入文件有数据输入*/
  			{
  				for (i = 0; i < IN_FILES; i++)
  				{
  					if (FD_ISSET(fds[i], &tmp_inset))
		  			{
                        /*对buf字符数组的MAX_BUFER_SIZE(1024)个字符赋初值为0*/
		  				memset(buf, 0, MAX_BUFFER_SIZE);
                                               
                        /*从fds[i]指向的文件中读1024个字符到buff字符数组*/
		  				real_read = read(fds[i], buf, MAX_BUFFER_SIZE);
		  		               

		  				if (real_read < 0)
		  				{
		  					if (errno != EAGAIN)
 /*在Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。例如,以 O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。*/

		  					{
		  						return 1;
		  					}
		  				}
		  				else if (!real_read)/*real_read等于0:已读到文件末尾*/
		  				{
		  					close(fds[i]);         /*关闭文件描述符*/
		  					FD_CLR(fds[i], &inset);/*将该文件描述符从文件描述符集inset清除出去*/
		  				}
		  				else   /*从文件描述符fds[i]中读到有效数据*/
		  				{
		  					if (i == 0)/*如果是标准输入文件(键盘)输入的数据,看是否是退出标记q或Q*/
		  					{
		  						if ((buf[0] == 'q') || (buf[0] == 'Q'))/*如果主程序收到q或Q字符,则程序终止*/
		  						{
		  							return 1;/*主程序退出*/
		  						}
                                                                else{printf("if you want to quit,please type q!\n"}

		  					}
		  					else 
                                                /*如果不是标准输入文件输入的数据,那只能是管道文件输入的数据,将cat从管道文件输入的字符串打印在屏幕上*/
		  					{
		  						buf[real_read] = '\0';/*在字符数组最后一位添加\0,使之成为字符串*/
		  						printf("%s", buf);    /*按照字符串格式打印输出字符数组buf*/
		  					}
		  				}
		  			} /* end of if */  					
  				} /* end of for */
  			}
  			break;  		
  			
  		} /* end of switch */ 		
  	} /*end of while */
  	
  	exit(0);
}

标准I/O变成

        用到的函数带缓冲,用法类似

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值