0
点赞
收藏
分享

微信扫一扫

系统调用IO

紫荆峰 2022-03-11 阅读 79
linuxc语言

标准IO依赖于系统IO进行实现;
文件描述符的概念
文件IO操作:open,close,read,write,lseek
将文件IO与标准IO的区别
IO的效率问题
文件共享
原子操作
程序中的重定向实现:dup, dup2
同步: sync, fsync, fdatasync
fcntl();
ioctl();
/dev/fd/目录

1.文件描述符的概念

整型数,为指针在数组中的下标,open()返回的文件描述符会优先选择当前可用范围内最小的使用,存在一个进程中;
每个进程都存在一个文件描述符数组;
在这里插入图片描述
解释:使用系统调用函数open()打开文件(inode),会内部产生一个类似FILE*的结构体,包含文件位置指针,打开次数计数器等信息。产生的结构体会对用户屏蔽,结构体首地址指针会保存到一个数组当中。交给用户的为当前存放指针的数组下标,即为文件描述符。默认0, 1, 2描述符分别对应stdin,stdout,stderr。数组大小查询:ulimiti -a。默认大小为1024。
特殊情况:
1.两个进程打开同一个文件,会产生两个结构体;
2.同一个文件在一个进程中打开两次,执行两次open函数,产生两个结构体,两个文件描述符,关联同一个文件。如果没有一定协议,对文件内容操作会产生竞争;关闭一个文件描述符,不影响另一个;
3.两个文件描述符指向同一个结构体(参见dup),关闭一个文件描述符,不会关闭结构体。原因在于结构体有计数器,反映当前结构体有几个指针引用。计数器为0时,才free结构体。
在这里插入图片描述 **寻找过程:**文件描述符->数组下标->指针->结构体->操作文件的信息
程序设计角度解释buf和cache: buf理解为写的缓冲区,cache理解为读的缓冲区;
**阻塞IO和非阻塞IO实现上的区别:**函数为同一套,在于有没有加参数O_NONBLOCK。
打印机打印东西,如果有文件过来,则打印;没有文件过来,则一直等待,阻塞IO; 有文件过来,则打印;没有文件过来,忙其他事情,非阻塞IO;

2.文件IO操作:open,close,read,write,lseek

r -> O_RDONLY
r+ -> O_RDWR
w -> O_WRONLY|O_CREAT|O_TRUNC(方式只写,文件不存在则创建,存在则截断)
w+ -> O_RWDR|O_TRUNC|O_CREAT(方式读写,有则清空,无则创建)

open函数:
在这里插入图片描述
open函数有两种形式,是通过可变参数实现的。
如何判别函数是重载函数还是可变参数函数?
多输入几个参数,看错误提示;可变参数一般警告,重载为报错;
gcc -wall

IO/sys/mycpy.c
 while(1)
    {
        ret = read(sfd, buf, BUFFSIZE); 
		printf("%d\n", ret);
        if(ret < 0)   
        {
            perror("read src_file");
            break;
        }
        if(ret == 0)
        {
            break;    
        } 
        //wet = write(dfd, buf, BUFFSIZE);   
		//检测如果没有一次写完,就继续写,直至把这次读到的数据全部写完
		//坚持写够字节.原因考虑信号的影响,信号会打断阻塞的系统调用,很可能在写不够
		//len字节的情况下就被打断
		pos = 0;
		while(ret > 0)
		{
			wet = write(dfd, buf+pos, ret);
			if(wet < 0)
			{
				perror("write des_file");
				//break;
				exit(1);
			}
			pos += wet;
			ret -= wet;
		}      
    }

3.将文件IO与标准IO的区别

系统调用IO :每调用一次,就实打实的从用户态到内核态执行一次,实时性高;
标准IO:操作只有在内存满/换行/fflush才会真正执行一次,合并系统调用
区别:响应速度:系统调用IO > 标准IO
吞吐量:系统调用IO < 标准IO
面试:如何使一个程序变快?
一分为二看问题,响应速度快,多用系统调用IO;吞吐量大,多用标准IO;
从用户体验看,变快一般指吞吐量变大;
提醒:系统调用IO和标准IO不可混用;
原因:标准IO对文件的改变并没有真正改变文件的内容,而是写到了缓冲区中;只有真正刷新的时候
才会真正改变文件内容。故标准IO对文件的操作不能与系统调用IO一一对应起来
参见IO/sys/ab.c

IO/sys/ab.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    putchar('a');
    write(1, "b", 1);

    putchar('a');
    write(1, "b", 1);

    putchar('a');
    write(1, "b", 1);

    exit(0);
}

输出结果为:bbbaaa

strace ./ab : 查看可执行文件ab的系统调用如何发生:
在这里插入图片描述
文件描述符与文件类型指针转换函数:

 int fileno(FILE* stream); //从文件指针转换到对应的文件描述符
 FILE* fdopen(int fd, const char* mode);//把一个已经打开的文件描述符转换成文件指针

4.IO的效率问题

习题:将IO/sys/mcpy.c程序进行更改,将BUFFSIZE的值成倍放大
1.观察进程所消耗的时间;
2.注意性能最佳拐点出现时的BUFFSIZE值;
3.何时程序会出问题;

//计算程序执行的时间
time ./mycpy <src_file> <des_file>

5.文件共享

多个任务共同操作一个文件或者协同完成任务
面试:写程序删除一个文件的第10行

思路1//定位到11行行首,读取内容。定位到10行行首,进行覆盖写;
//4次系统调用
//最后需要把文件截短
while()
{
lseek11 + read + lseek10 + write
}

思路2:
当前进程空间把文件打开两次 
1 -> open r ->fd1 -> lseek 11
2 -> open r+  ->fd2 -> lseek 10  //采用r+ 是为了检查文件是否存在

while()
{
1->fd1-> read
2->fd2-> write 
}

思路3
process1 -> open -> r
process -> open -> r+

process1 -> read -> process2 -> write

思路2和3都是为了减少系统调用,打开文件两次;

补充函数:

//未打开的文件截断到多长
int truncate(const char* path, off_t length);
//已打开的文件截断到多长
int ftruncate(int fd, off_t length);

6.原子操作

原子操作:不可分割的操作
原子操作的作用:解决竞争和冲突
如解决tmpnam()的非原子操作问题

7.程序中的重定向实现:dup, dup2

#include <unistd.h>
//拷贝一份oldfd的副本,放到当前可用范围内最小位置上去
//和oldfd指向同一个文件结构
int dup(int oldfd);

//newfd作为oldfd的副本,如果newfd被占用,则会先释放newfd 
//如果newfd和oldfd是同样值的文件描述符,则不做任何操作
int dup2(int oldfd, int newfd);
IO/sys/dup.c
//将puts("hello!")从标准输出重定向输出到tmp/out中
int flag;
   
   //思路1
	/*
   flag =  close(1);
   if(flag < 0)
   {
        perror("close()");
        exit(1);
   }
    
	
    int fd;
    fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if(fd < 0)    
    {
        perror("open");
        exit(1);
    }
	*/
	
	//思路2
	//缺陷:
	//1.当前进程文件描述符默认只有0,没有1,则open后,fd就为1,在close(1)出会出现问题;
	//2.有另一个任务在跑,close(1)语句后,没有执行dup之前,另一个任务打开一个新的文件,
	//则描述符1被分配走 
	//产生缺陷原因:close(1) + dup(fd) 操作非原子
	/*
	int fd;
    fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if(fd < 0)    
    {
        perror("open");
        exit(1);
    }
	close(1);
	dup(fd);
	close(fd);
	*/
	
	//思路3
	//还是有写缺陷,不应该从main的角度来写
	//应该从模块的角度来写,更改完文件描述符1后,需要在puts操作结束后,复原现场
	//1.不要有内存泄露 2.不要产生越界 3.永远当作在写小模块,不是在写main函数
	int fd;
    fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if(fd < 0)    
    {
        perror("open");
        exit(1);
    }
	
	dup2(fd, 1);
	
	if(fd != 1)
		close(fd);
	
	//重新打开dev的标准输出
	 
/***********************************************/
    puts("hello!");
举报

相关推荐

0 条评论