操作系统与系统编程(1)——文件

去年看的APUE最近发现忘得不少,重温顺便记录一下,最近还是懒了,决定系统的写一下勉励自己,加油!!!

 

目录

文件I/O:

内核缓冲区和用户缓冲区

标准I/O和文件I/O:

网络I/O的buffer:

PCB进程管理模块:

文件结构体:

文件I/O api:

测试代码:

文件系统:

一个ext2的文件系统:

文件控制api

测试代码:

参考:


文件I/O:

内核缓冲区和用户缓冲区

用户进程和操作系统的关系

这是一个计算机系统运行时的简化模型,我们把所有运行在操作系统上的进程称为用户进程,它们都运行在用户空间(把操作系统运行的空间成为系统空间)

内核态和用户态(kernel mode和user mode),在内核态可以访问系统资源,而在用户进程中是无法被直接访问的,只能通过操作系统来访问,所以也把操作系统提供的这些功能成为:“系统调用”。

提供这些限制的基础就是cpu提供的内核态和用户态。比如intel x86 CPU有四种不同的执行级别0-3,linux只使用了其中的0级和3级分别来表示内核态和用户态。

用户缓冲区:的目的是为了减少系统调用次数,从而降低操作系统在用户态与核心态切换所耗费的时间。

用户进程通过系统调用访问系统资源的时候,需要切换到内核态,而这对应一些特殊的堆栈和内存环境,必须在系统调用前建立好。而在系统调用结束后,cpu会从核心模式切回到用户模式,而堆栈又必须恢复成用户进程的上下文。而这种切换就会有大量的耗时。

内核缓冲区: 当一个用户进程要从磁盘读取数据时,内核一般不直接读磁盘,而是将内核缓冲区中的数据复制到进程缓冲区中。但若是内核缓冲区中没有数据,内核会把对数据块的请求,加入到请求队列,然后把进程挂起,为其它进程提供服务,读外设不需要cpu参与,一般DMA(直接内存读取)

等到数据已经读取到内核缓冲区时,把内核缓冲区中的数据读取到用户进程中,才会通知进程,当然不同的io模型,在调度和使用内核缓冲区的方式上有所不同。

你可以认为,read是把数据从内核缓冲区复制到进程缓冲区。write是把进程缓冲区复制到内核缓冲区。

当然,write并不一定导致内核的写动作,比如os可能会把内核缓冲区的数据积累到一定量后,再一次写入。这也就是为什么断电有时会导致数据丢失。

所以说内核缓冲区,是为了在OS级别,提高磁盘IO效率,优化磁盘写操作。

缓存和缓冲区

CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,因为cpu的计算速度要比内存的读写速度快很多,而把这些可能会被重复访问到的数据存储于cpu缓存中,就会提高读取速度。可以说缓存是cpu和内存之间的临时存储器。

也就是说,buffer是因为减少调用次数,集中调用,提高系统性能。而cache是将读取过的数据保存起来,重新读取时若命中(找到需要的数据)就不要去读硬盘了,若没有命中就读硬盘。

标准I/O和文件I/O:

标准I/O(高级I/O)是ANSI C 提供的的I/O库,在stdio.h中,有很多细节,缓存分三种。(在文件I/O上缓存封装,直接操作缓冲区,必要时才操作文件,减少系统调用

  1. 全缓存:当填满缓冲区后才进行实际I/O操作。
  2. 行缓存:遇到换行符,标准I/O才执行I/O操作。
  3. 不带缓存:直接I/O操作。
  4. 标准I/O函数:fopen、fclose、fread、fgets、fwrite、fputs)操作文件指针(不是指向实际文件,包含缓冲区信息)失败返回nullptr

 

文件I/O(低级I/O),unbuffed I/O无缓存,直接调用内核系统调用,每次直接操作实际文件,但频繁系统调用会导致系统开销。

文件I/O函数: (open、close、read、write)操作文件描述符,失败返回-1

 

网络I/O的buffer:

当某个进程/线程需要某段数据时,它只能在用户空间中属于它自己的内存中访问、修改,这段内存暂且称之为app buffer(用户缓冲区)。

假设需要的数据在磁盘上,那么进程首先得发起相关系统调用,通知内核去加载磁盘上的文件。但正常情况下,数据只能加载到内核的缓冲区,暂且称之为kernel buffer。数据加载到kernel buffer之后,还需将数据复制到app buffer。到了这里,进程就可以对数据进行访问、修改了。

 

 

现在的存储设备(包括网卡)基本上都支持DMA操作。(direct memory access,直接内存访问)简单地说,就是内存和设备之间的数据交互可以直接传输,不再需要计算机的CPU参与,而是通过硬件上的芯片(可以简单地认为是一个小cpu)进行控制。

再说kernel buffer和app buffer之间的复制方式,这是两段内存空间的数据传输,只能由CPU来控制。

所以,在加载硬盘数据到kernel buffer的过程是DMA拷贝方式,而从kernel buffer到app buffer的过程是CPU参与的拷贝方式。

PCB进程管理模块:

 

在linux 中每一个进程都由task_struct 数据结构来定义. task_struct就是我们通常所说的PCB.她是对进程控制的唯一手段. 当我们调用fork()时, 系统会为我们产生一个task_struct结构。然后从父进程那里继承一些数据, 并把新的进程插入到进程树中, 以待进行进程管理。

文件结构体:

  内核每打开一个文件就创建一个struct file结构体与之关联,并将其传递给所有操作此文件的函数,当文件的所有实例都关闭后内核释放掉这个结构体。结构体两个重要字段:文件描述符和缓冲区。

 文件描述符fd:fd只是一个小整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp

文件描述符的操作(如:open)返回的是一个文件描述符,内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用;

而流(如:fopen)返回的是一个FILE结构指针, FILE结构是包含有文件描述符的可以看作是对fd系统调用的封装, 它的优点是带有I/O缓存

文件I/O api:

open close read write lseek

int open(const char *pathname,int flags,int perms)

用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。

int close(int fd)

用于关闭一个被打开的文件,成功返回0,失败返回1。

ssize_t read(int fd, void *buf, size_t count);

从文件读,返回所读取的字节数;0(读到EOF);-1(出错)。

ssize_t write(int fd, void *buf, size_t count);

功能:write 函数向 filedes 中写入 count 字节数据,数据来源为 buf 。返回值一般总是等于 count,否则就是出错了。写入文件的字节数(成功);-1(出错)

off_t lseek(int fd, off_t offset,int whence);

用于在指定的文件描述符中将文件指针定位到相应位置。

测试代码:

//测试open函数
/*************************************************************************
	> File Name: open.c
	> Author: ma6174
	> Mail: ma6174@163.com 
	> Created Time: 2018年08月22日 星期三 11时14分57秒

 ************************************************************************/

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


int main(int argc,char *argv[])
{
    int fd;
	char buf[1024] = "I am happy";
	if(argc < 2)
	{
		printf("./app,filename\n");
		exit(1);
	}
	umask(0000);
	//fd = open(argv[1],O_CREAT|O_RDWR|O_EXCL,0644);
	//O_CREAT creat file O_EXCL if file has exist ,then return false -1
	
	fd = open(argv[1],O_RDWR|O_APPEND);
	printf("fd = %d\n",fd);
    write( fd,buf, strlen(buf));
	close(fd);
	return 0;
}


//测试非阻塞
/*************************************************************************
	> File Name: unblock.c
	> Author: ma6174
	> Mail: ma6174@163.com 
	> Created Time: 2018年08月22日 星期三 19时00分02秒
 ************************************************************************/
#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>

#define MSG_TRY "try again\n"
int main(void)
{
	char buf[10];
	int fd,n;
	fd = open("/dev/tty",O_RDONLY|O_NONBLOCK);//open curent  tty in the way of unblock
	if(fd < 0)
	{
		perror("open/dev/tty");//open failed
		exit(1);
	}
tryagain://tty open successfully
	n = read(fd,buf,10);
	if(n < 0)//reading fail
	{
		if(errno == EAGAIN)//no message  
		{
			sleep(1);
			write(STDOUT_FILENO,MSG_TRY,strlen(MSG_TRY));
			goto tryagain;
		}
	    perror("open/dev/tty");//other err
		exit(1);
	}
	write(STDOUT_FILENO,buf,n);//reading successfully,then write on the sreen
	close(fd);
	return 0;
}



文件系统:

更改文件属性 stat chomd chown link unlink symlink readlink rename chdir mkdir rmdir dup

一个ext2的文件系统:

 

文件控制api

int stat(const char *file_name, struct stat *buf);

通过文件名filename获取文件信息,并保存在buf所指的结构体stat中,执行成功则返回0,失败返回-1,错误代码存于errno

int chmod(const char *filename,int mode)

修改文件权限参数: filename为文件名,mode为文件权限,八进制数。

返回值:成功返回0,失败返回-1,同时errno会被设置为合适值。

int chown(const char *path,uid_t owner,gid_t group)

修改文件所有者和所属组。参数:path为文件路径。 owner,用户IDgroupID

返回值:成功返回0,失败返回-1,同时errno会被设置为一个合适的值。

int link(const char *oldpath,const char *newpath);

函数说明:link()函数为一个已经存在的文件创建一个新的链接(“硬链接”)

成功,返回0,一旦错误,返回-1。并且erron被设置了结果

unlink()会删除参数pathname 指定的文件. 如果该文件名为最后连接点, 但有其他进程打开了此文件, 则在所有关于此文件的文件描述符皆关闭后才会删除. 如果参数pathname 为一符号连接, 则此连接会被删除。

int symlink(const char *oldpath, const char *newpath);

描述:symlink() 创建一个符号链接,软连接指向oldpath

readlink符号链接所指向的文件名字,不读文件内容

ssize_t readlink(const char *path, char *buf, size_t bufsiz)

mkdir rmdir等目录操作符

文件重定向:

int dup(int oldfd);

int dup2(int oldfd,int newfd);

描述复制一个现有的文件描述符,使得新老文件描述符指向同一个file struct,dup复制给最小未使用文件描述符,文件引用计数+1。一个文件open两次则有两个file struct有不同的文件状态和读写位置。

测试代码:

/*************************************************************************
	> File Name: stat.c
	> Author: ma6174
	> Mail: ma6174@163.com 
	> Created Time: 2018年08月24日 星期五 18时32分32秒
 ************************************************************************/

//仿写运用stat 仿写ls

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




//stat is command and func; stat file:will return file'Inode
//stat --->ls
void file_type(struct stat s,char buf[])
{
	 if(( s.st_mode & S_IFDIR) == S_IFDIR)
		 buf[0] = 'd';
	 if(( s.st_mode & S_IFLNK) == S_IFLNK)
		 buf[0] =  'l';
	 if(( s.st_mode & S_IFREG) == S_IFREG)
		 buf[0] = '-';


	 if(( s.st_mode & S_IRUSR) == S_IRUSR)
		 buf[1] = 'r';
	 else
		 buf[1] = '-';
	 if(( s.st_mode & S_IWUSR) == S_IWUSR)
		 buf[2] = 'w';
	 else
		 buf[2] = '-';
	 if(( s.st_mode & S_IXUSR) == S_IXUSR)
		 buf[3] = 'x';
	 else
		 buf[3] = '-';
	 if(( s.st_mode & S_IRGRP) == S_IRGRP)
		 buf[4] = 'r';
	 else
		 buf[4] = '-';
	 if(( s.st_mode & S_IWGRP) == S_IWGRP)
		 buf[5] = 'w';
	 else
		 buf[5] = '-';
	 if(( s.st_mode & S_IXGRP) == S_IXGRP)
		 buf[6] = 'x';
	 else
		 buf[6] = '-';
	 if(( s.st_mode & S_IROTH) == S_IROTH)
		 buf[7] = 'r';
	 else
		 buf[7] = '-';
	 if(( s.st_mode & S_IWOTH) == S_IWOTH)
		 buf[8] = 'w';
	 else
		 buf[8] = '-';
	 if(( s.st_mode & S_IXOTH) == S_IXOTH)
		 buf[9] = 'x';
	 else
		 buf[9] = '-';
	 buf[10]= '\0';
	
}


int main(int argc,char*argv[])
{
int n;
struct stat file_stat;
char buf[10] = {0};
	if(argc < 2)
	{
		printf("./app filename");//perror() when fun error; logical error using printf()
		exit(1);//if correct,exit(0);else no zero;
	}
  if( stat(argv[1], &file_stat)<0)
  {
	  perror("stat:");
	  exit(1);
  }
  else
  {
	  printf("filename:%s  size: %ld\n",argv[1],file_stat.st_size);
	  printf("BlockSize for IO:%ld\n",file_stat.st_blksize);
      file_type(file_stat,buf);
	  printf("file_type %s",buf);
	  
  }
	return 0;
}

/*************************************************************************
	> File Name: dm06_dup.c
	> Author: ma6174
	> Mail: ma6174@163.com 
	> Created Time: 2018年08月26日 星期日 16时51分22秒
 ************************************************************************/
//重定向标准输出和文件输入

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

int main()
{
	int fd,save_fd;
	char msg[] = "This is a test\n";
	fd = open("wch_test.txt",O_RDWR|O_CREAT,0600);
	if(fd < 0)
	{
		perror("open:");
		exit(-1);
	}
	save_fd = dup(STDOUT_FILENO);
	dup2(fd,STDOUT_FILENO);
	close(fd);//fd point STDOUT_FILENO ,the cnt minus 1
	write(STDOUT_FILENO,msg,strlen(msg));
	dup2(save_fd,STDOUT_FILENO);
	write(STDOUT_FILENO,msg,strlen(msg));
	close(save_fd);
	return 0;

}

 

 

参考:

APUE

linux_sys

https://www.cnblogs.com/0xcafebabe/p/4423699.html

https://www.cnblogs.com/0xcafebabe/p/4426126.html

https://www.cnblogs.com/0xcafebabe/p/4430632.html

https://www.cnblogs.com/alantu2018/p/8461749.html

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值