Linux编程基础 2.1:Linux文件系统与操作

0 系统调用

0.1 什么是系统调用

系统调用函数属于操作系统的一部分,是为了提供给用户进行操作的接口(API函数);
使得用户态运行的进程与硬件设备(如CPU、磁盘、打印机、显示器)等进行交互。
常见的系统调用有:

  • write
  • read
  • open

0.2 什么是库函数

库函数可以理解为是对系统调用函数的一层封装。库函数可分为两类:

  • 一类是C语言标准库函数;
  • 一类是编译器特定的库函数。

尽管系统函数执行效率是比较高效而精简的,但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数,再将许多这类的函数放在一个文件(库)一般放在 .lib文件。最后再供程序员使用。

使用的时候包含头文件就可以使用其中的库函数了
#include<stdio.h>
就可以使用常见的库函数:

  • printf
  • fwrite
  • fread
  • fopen

0.3 将hello写入到文件1.txt流程

  • 首先fopen打开文件;
  • fwrite参数附上要写入的内容;
  • 文本内容来到C标准缓冲区;
  • 如果满足条件就刷新C标准缓冲区,调用系统函数write进行写(注:满了就会自动刷新);
  • write却只是把要写入的内容写到内核缓冲区;
  • 如果内核缓冲区满足条件就刷新内核缓冲区,系统调用sys_write将缓冲区内容写入到磁盘(注:有进程会定时刷新内核缓冲区);
  • 此时如果有进程要读取1.txt文件内容,发现内核缓冲区就有这个文件内容,就直接从内核缓冲区读取。
    在这里插入图片描述

0.4 为什么要有缓冲区(补充)

定义:缓冲区就是内存里的一块区域,把数据先存内存里,然后一次性写入硬盘中的文件,类似于数据库的批量操作
好处:减少对硬盘的直接操作,硬盘的执行速度为毫秒级别,内存为纳秒级别。在硬盘直接操作读写效率太低。

0.5 C标准缓冲区和内核缓冲区的区别

  • C语言标准库函数fopen()每打开一个文件时候,其都会对应一个单独一个缓冲区;
  • 而内核缓冲区是公用的。

1 文件I/O

open()
read()
write()
lseek()
close()

1.1 open函数

#include <fcntl.h>
int open(const char *pathname, int flags[, mode_t mode);

open函数参数说明:

  • pathname:待打开文件的文件路径名;
  • flags:访问模式,常用的宏有:
    – O_RDONLY:只读
    – O_WRONLY: 只写
    – O_RDWR: 读写
    – O_CREAT: 创建一个文件并打开
    – O_EXCL: 测试文件是否存在,不存在则创建
    – O_TRUNC: 以只写或读写方式成功打开文件时,将文件长度截断为0
    – O_APPEND: 以追加方式打开文件

只有第二个参数flags = O_CREAT,第三个参数才会被用于设置新文件的权限,取值如下:

  • S_IRWXU: 文件所有者,读、写、执行
  • S_IRUSR: 文件所有者,读
  • S_IWUSR: 文件所有者,写
  • S_IXUSR: 文件所有者,执行
  • S_IRWXG: 文件所属组,读、写、执行
  • S_IRGRP: 文件所属组,读
  • S_IWGRP: 文件所属组,写
  • S_IXGRP: 文件所属组,执行
  • S_IRWXO: 其他人,读、写、执行
  • S_IROTH: 其他人,读
  • S_IWOTH: 其他人,写
  • S_IXOTH: 其他人,执行

返回值说明:

  • 调用成功,返回一个文件描述符
  • 不成功,返回-1

例: 创建一个文件

open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode);
or
int create(const char *pathname, mode_t mode);

【案例 1】创建一个文件,设置权限为0777。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(){
	int tempFd;
	tempFd = open("abc", O_CREAT, 0777);
	printf("fd = %d\n", tempFd);
	return 0;
}//of main 

问题:(1) 为什么fd = 3? (2) 为什么abc文件权限是775?
[答]: (1) 有stdin, stdout, stderr三个文件默认打开; (2) 文件权限是设置的权限和掩码进行与运算后的结果。
在这里插入图片描述

【案例 2】最多能打开多少个文件?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[]){
	int tempFd;
	char tempName[1024];
	int i = 0;
	while (1) {
		sprintf(tempName, "file%d", ++i);
		tempFd = open(tempName, O_CREAT, 0777);
		if (tempFd == -1) {
			exit(1);
		}//of if
		printf("%d\n", i);
	}//of while
	return 0;
}//of main 

问题:为什么是1021?不是1024?
在这里插入图片描述
答:因为有stdin, stdout, stderr三个文件默认打开

几个有用的命令:

  • cat /proc/sys/fs/file-max
    查看当前系统允许打开最大文件个数
  • ulimit –a
    当前默认设置最大打开文件个数1024
  • ulimit –n 4096
    修改默认设置最大打开文件个数为4096

1.2 read函数

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

read函数参数说明:

  • fd: 从open或create函数返回的文件描述符
  • buf: 缓冲区
  • count: 读取数据的字节数

返回值说明:

  • ssize_t: 有符号的size_t,有三种返回值
    – 正数:请求读取的字节数
    – 0: 文件长度有限,若读写位置距文件末尾只有20字节,该函数请求读取30字节,则第一次读取时返回值为20,第二次读取时,返回0
    – -1: 读取文件出错

特殊说明: read函数从设备或网络中读数据,如从终端读取数据,终端写入数据没回车,这些数据不会传给read函数,read函数就会一直阻塞;如从网络端读取数据,网络通信的socket文件没有数据,read函数同样会阻塞。

【案例 1】阻塞读,超过字符数会出现什么问题。

#include <unistd.h>
#include <stdlib.h>
int main(void) {
    char buf[10];//十个字符
    int n;
    n = read(STDIN_FILENO, buf, 10);
    if (n < 0) {
        perror("read STDIN_FILENO");
        exit(1);
    }//of if
    write(STDOUT_FILENO, buf, n);
    return 0;
}//of main

问题:为什么会出现命令找不到?
在这里插入图片描述
答:超过10个字符,read只读取了10个字符,剩下的字符保存在内核的终端设备输入缓冲区
a.out退出,Shell进程继续从终端读走剩下的字符d和换行符,把它当成一条命令,结果发现执行不了。

1.3 write函数

#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);

write函数参数说明: 同read函数
返回值说明: 返回写入的字节数或者-1并设置errno
特殊说明: 向终端或网络端写数据时,可能会进入阻塞状态

【案例 1】

 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 int  fd;
 char text[]="Welcome come to SWPU";    
 fd=open("tmp.txt",O_WRONLY|O_CREAT,0777);
 write(fd,text,strlen(text));

1.4 lseek函数

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

lseek函数参数说明:

  • fd: 从open或create函数返回的文件描述符
  • offset: 对文件偏移量的设置,参数可正可负
  • whence: 控制设置当前文件偏移量的方法
    – whence = SEEK_SET: 偏移到文件头+ 设置的偏移量
    – whence = SEEK_CUR: 偏移到当前位置+设置的偏移量
    – whence = SEEK_END: 偏移到文件尾+设置的偏移量

返回值说明:

  • 设置成功:返回新的偏移量
  • 不成功:-1

【案例1】返回当前的偏移量

int fd,ret;
fd=open("hello1.txt",O_RDWR);
ret=lseek(fd,0,SEEK_CUR);
printf("%d\n",ret);

【案例2】返回文件大小

int fd,ret;
fd=open("hello1.txt",O_RDWR);
ret=lseek(fd,0,SEEK_END);
printf("%d\n",ret);

【案例3】扩充文件大小:特别注意扩充文件大小后 需要写入内容 否则扩充不生效

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
	int fd,ret;
	char a[]="Welcome to SWPU";
	fd=open("hello1.txt",O_RDWR|O_CREAT,0777);
	ret=lseek(fd,1000,SEEK_END);
	write(fd,a,strlen(a));
	printf("%d\n",ret);
	return 0;
}

使用ls -l 命令可以查看扩充后的字节大小
在这里插入图片描述

1.5 close函数

#include <unistd.h>
int close(int fd);

返回值说明:

  • 成功:返回0
  • 不成功:-1

2 案例

【案例 4】 使用open函数打开或创建一个文件,将文件清空,使用write函数在文件中写入数据,并使用read函数将数据读取并打印。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(){
	int tempFd = 0;
	char tempFileName[20] = "test.txt";
	//Step 1. open the file.
	tempFd = open(tempFileName, O_RDWR|O_EXCL|O_TRUNC, S_IRWXG);
	if(tempFd == -1){
		perror("file open error.\n");
		exit(-1);
	}//of if
	//Step 2. write the data.
	int tempLen = 0;
	char tempBuf[100] = {0};
	scanf("%s", tempBuf);
	tempLen = strlen(tempBuf);
	write(tempFd, tempBuf, tempLen);
	close(tempFd);
	//Step 3. read the file
	tempFd = open(tempFileName, O_RDONLY);
	if(tempFd == -1){
		perror("file open error.\n");
		exit(-1);
	}//of if
	off_t tempFileSize = 0;
	tempFileSize = lseek(tempFd, 0, SEEK_END);
	lseek(tempFd, 0, SEEK_SET);
	while(lseek(tempFd, 0, SEEK_CUR)!= tempFileSize){
		read(tempFd, tempBuf, 1024);
		printf("%s\n", tempBuf);
	}//of while
	close(tempFd);
	return 0;
}//of main

3 虚拟空间地址/PCB进程控制块/文件描述符表

对于每一个进程,系统都会为其分配一个0-4G的虚拟空间地址:

  • 0-3G为用户空间,用户可操作部分;
  • 3~4G为内核,其中PCB控制块也存在内核中,PCB结构体包括文件标识符表以及其他很多信息(补充:每一个进程都有一个PCB)。

在这里插入图片描述
文件描述符表:结构体 PCB 的成员变量 file_struct *file 指向文件描述符表。
从应用程序使用角度,该指针可理解记忆成一个字符指针数组,下标 0、1、2、…,通过该指针找到文件结构体。本质是一个键值对 0、1、2、…都分别对应具体地址。但键值对使用的特性是自动映射,我们只操作键不直接使用值。
新打开文件返回文件描述符表中未使用的最小文件描述符。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HenrySmale

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值