编写代码并运行:
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main()
5 {
6 while(1)
7 {
8 sleep(1);
9 printf("我是一个进程: %d\n",getpid());
10 }
11 return 0;
12 }
cwd
叫做当前进程的current working directory
,表示当前进程所在的工作目录。
系统中有一个可以更改目录的方法,chdir
,谁调用chdir
,就更改谁的当前工作目录。
原目录:
[likaixuan1@VM-4-13-centos 20230404-基础IO]$ pwd
/home/likaixuan1/106/20230404-基础IO
[likaixuan1@VM-4-13-centos 20230404-基础IO]$
改改代码:
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main()
5 {
6 chdir("/home/likaixuan1");
7 while(1)
8 {
9 sleep(1);
10 printf("我是一个进程: %d\n",getpid());
11 }
12 return 0;
13 }
输入指令:[likaixuan1@VM-4-13-centos 20230404-基础IO]$ ls /proc/32714 -al
当前进程的cwd变成了/home/likaixuan1
,当前工作目录被修改了。
补充问题:
为什么我们自己写的shell,cd的时候,路径没有变化?
fork创建子进程,子进程有自己的工作目录,执行cd更改的是子进程的目录,子进程执行完毕继续用的是父进程即shell,子进程改了和父进程没有关系,我们看到的是父进程。
文件在磁盘,磁盘是硬件,只有操作系统可以管理,那么所有的人想要访问磁盘就不能跨过操作系统,必须得使用操作系统提供的文件级别的系统调用的接口。操作系统只有一个,所以上层语言无论如何变化,a.库函数底层必须调用系统调用接口。b.库函数可以千变万化,但底层不变。
r+:读写(不存在就出错)。
w+:读写(不存在就创建)。
a:追加(append),写在文件末尾。
1 #include <stdio.h>
2 #include <unistd.h>
3
4
5 #define FILE_NAME "log.txt"
6 int main()
7 {
8 FILE *fp = fopen(FILE_NAME,"w");
9 if(NULL == fp)
10 {
11 perror("fopen");
12 return 1;
13 }
14
15 fclose(fp);
16
17 }
编译运行,我们发现创建了log.txt.文件。
运行前:
运行后:
此时文件大小为0,也就证明了w就是写入,但是文件不存在自动会创建。
现在我们想往里面写入5句话,用fprintf()
接口。
1 #include <stdio.h>
2 #include <unistd.h>
3
4
5 #define FILE_NAME "log.txt"
6 int main()
7 {
8 FILE *fp = fopen(FILE_NAME,"w");
9 if(NULL == fp)
10 {
11 perror("fopen");
12 return 1;
13 }
14
15 int cnt = 5;
16 while(cnt)
17 {
18 fprintf(fp,"%s:%d\n","hello
19 }
我们也可以以r方式打开文件,这个叫做读取,
修改代码:
1 #include <stdio.h>
2 #include <unistd.h>
3
4
5 #define FILE_NAME "log.txt"
6 int main()
7 {
8 FILE *fp = fopen(FILE_NAME,"w");
9 if(NULL == fp)
10 {
11 perror("fopen");
12 return 1;
13 }
14
15 char buffer[64];
16 while(fgets(buffer,sizeof(buffer)-1,fp) != NULL)
17 {
18 puts(buffer);//把读的字符串,显示到显示器上。
19 }
20
21 fclose(fp);
22 }
我们使用fgets
,表示以行为单位,从特定的文件当中读取数据,将读到的数据放到s所指向的缓冲区的当中。
运行程序,就可以按照文本行的方式把文件读上去了:
我们以a方式打开,继续修改代码:
1 #include <stdio.h>
2 #include <unistd.h>
5 #define FILE_NAME "log.txt"
6 int main()
7 {
10 FILE *fp = fopen(FILE_NAME,"a");
11 if(NULL == fp)
12 {
13 perror("fopen");
14 return 1;
15 }
23 int cnt = 5;
24 while(cnt)
25 {
26 fprintf(fp,"%s:%d\n","hello file",cnt--);
27 }
28 fclose(fp);
36 }
运行程序:
这就叫做追加。
注意细节:以w方式单纯打开文件,c语言会自动清空内部的数据。
操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问。
我们在系统当中实际上用fopen打开文件底层调用的系统调用接口是open
头文件是:
#include
#include
#include
我们先介绍:
int open(const char *pathname, int flags, mode_t mode);
我们创建文件时权限是什么由第三个参数mode_t mode
告诉。第一个参数就是当前路径。
返回成功会返回给我们一个文件描述符file descriptor
,失败了-1被设置,errno也会被设置。
O_RDONLY…这些东西叫做宏。
我们一般用C传标记为,一个整数传一个标记位。我们知道一个整数有32个比特位,也就意味着我们可以通过比特位传递选项。
操作系统如何使用比特位传递选项?
一个比特位,一个选项,比特位位置不能重复。
#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
#define FOUR 0x8
每一个宏对应的数值,只有一个比特位是1,彼此位置不重叠。
有的开源项目是这样写的:
#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
代码:
1 #include <stdio.h>
2 #include <unistd.h>
3 #define FILE_NAME "log.txt"
4
5 //每一个比特位对应的数值,只有一个比特位时1,彼此位置不重叠。
6 #define ONE (1<<0)
7 #define TWO (1<<1)
8 #define THREE (1<<2)
9 #define FOUR (1<<3)
10
11 void show(int flags)
12 {
13 if(flags & ONE) printf("one\n");
14 if(flags & TWO) printf("two\n");
15 if(flags & THREE) printf("three\n");
16 if(flags & FOUR) printf("four\n");
17 }
18
19
20 int main()
21 {
22 show(ONE);
23 printf("-------------------------\n");
24 show(TWO);
25 printf("-------------------------\n");
26 show(ONE | TWO);
27 printf("-------------------------\n");
28 show(ONE | TWO | THREE);
29 printf("-------------------------\n");
30 show(ONE | TWO | THREE | FOUR);
31 printf("-------------------------\n");
32 }
编译运行:
再次看:int open(const char *pathname, int flags, mode_t mode);
第一个参数是对应的路径起始就是我们要打开的文件。第二个是按标记位传参,O_RDONLY
表示只读,O_WRONLY
表示只写,O_RDWR
表示读写。此时这就表示不同的标记位(宏),这些宏是通过不同的比特位来表示不同的含义的。
补充:O_TRUNC
:表示对文件内容做清空。O_APPEND
:表示追加。
下面我们看看int open(const char *pathname, int flags, mode_t mode);
怎么用?
close是关闭文件描述符的。
代码:
#include
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
int fd = open(FILE_NAME,O_WRONLY | O_CREAT,0666);
if(fd < 0)
{
perror("open");
return 1;
}
close(fd);
}
编译运行:
运行前:
运行后:
此时log.txt的权限是664(-rw-rw-r–)和我们用C语言创建的一样。
因为umask默认是0002所以我们的权限最终是664
我们可以设置umask为0,默认权限就是666了。
此时权限就是(-rw-rw-rw-),需要注意的是我们改的是我们自己的文件权限并不影响shell。
向文件写入:
#include
ssize_t write(int fd, const void *buf, size_t count);
第一个参数fd就是我们想往哪个文件去写,第二个是我们想写的时候对应的缓冲区数据所在地,第三个是缓冲区当中字节个数。返回值是我们所写的字节数。
以前学C的时候我们了解到读写文件两种方案:文本类和二进制类,这里的文件读取分类是语言给我们提供的。传const void *
操作系统看来都是二进制位。
代码:
#include
#include
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
int cnt = 5;
char outBuffer[64];
while(cnt)
{
sprintf(outBuffer, "%s:%d\n","aaaa",cnt--);
write(fd,outBuffer,strlen(outBuffer));//向文件中写入string的时候 不用+1
close(fd);
}
编译运行就完成了写入:
追加的话:需要使用O_APPEND
。
修改下面代码:
int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_APPEND,0666);
编译运行看下面实验结果:
想要读取文件:得使用O_RSONLY
read
这个接口表示从一个文件描述符中读取文件,返回ssize_t(有符号整数,可以大于小于等于0)
ssize_t read(int fd, void *buf, size_t count);
从特定的文件(fd)中读到缓冲区里(buf),期望读count个。
代码:
#include
#include
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open");
return 1;
}
char buffer[1024];
ssize_t num = read(fd,buffer,sizeof(buffer)-1);
if(num > 0)
{
buffer[num] = 0;
printf("%s",buffer);
}
close(fd);
}
此时就可以读出文件内容了。
如上,系统调用接口:open / close / write / read
对应C语言是库函数接口:fopen / fclose / fwrite / fread
访问文件时必须使用系统调用接口,C库函数是封装了系统调用接口的。
进程可以打开多个文件,系统中一定会存在大量被打开的文件,被打开的文件要被操作系统管理。
如何管理?(先描述,再组织)
操作系统为了管理对应的文件,必定要为文件创建对应的内核数据结构标识文件,这个内核数据结构是struct file{}
,它包含了文件的大部分属性。
三个标准输入输出流:
stdin :键盘
stdout :显示器
stderr :显示器
FILE *fp = fopen();
FILE是一个结构体,我们底层访问文件时必须用系统调用,而系统调用接口访问文件时必须用文件描述符。所以这个结构体里必定有一个字段叫做文件描述符。
我们自己打开的文件或文件描述符是从3开始的,原因就是0 1 2 默认被占用,我们C语言FILE类型指针也封装了操作系统内的文件描述符。
为什么数字是0 1 2开始的?
进程执行了一个接口,接口叫做open,系统在启动的时候默认启动了三个文件:键盘、显示器、显示器。操作系统要把log.txt
加载到对应的内存里,那么首先并不是把内容直接加载到内存里,而是先描述这个文件,那么log.txt
对应的结构叫做struct file
,struct file{}
保存的是文件的属性。PCB里有一个struct files_struct *file
这样的一个指针,这个指针指向了一个属于进程的数据结构对象,叫做struct files_struct
这个结构体是专门构建进程和文件对应关系的结构体,这个结构体里面包含了一个数组,这个数组的名字叫struct file* fd_array[]
这是一个指针数组,数组里面所有的成员都是struct file*
的指针,数组就有下标,分别都可以指向文件,0指向键盘,1指向显示器,2指向显示器,当再打开文件时从上往下找没有被使用的文件标识符了,所以从磁盘中把文件加载进来,把对象构建好,然后把这个对象的地址填到3号文件描述符里,此时3号文件描述符就指向新打开的文件了,然后我们再把3号文件描述符通过系统调用给用户返回,就得到了一个数字就叫做3,所以当一个进程在访问文件时它需要传入3,通过系统调用,操纵系统会找进程的文件描述符表,根据它找到对应的文件,文件找到了就可以对文件进行操作了。