# 第1章-Linux系统编程入门

​ 牛客网上『高性能服务器开发」笔记

<font style="background: MediumSpringGreen">
<font style="background:yellow">
1
2

# 目录

[TOC]

- open
- read 
- write
- lseek
- stat
- lstat
1
2
3
4
5
6

# 0.课程介绍

# 1.为什么要学习这门课?

C++相关岗位开发非常多,比如

  • Linux服务器开发
  • 桌面客户端开发
  • 游戏开发
  • 网络安全方向的
  • 音视频开发,非常多
  • 智能驾驶

调研:

  • 大多数是做Linux服务器开发,因为他的相关岗位最多,相对其他岗位比较容易上手的。这个是学习这门课的第1个原因
  • 第2个原因:求职过程当中,和Linux服务开发相关的面试题也是最多的。
  • 在这,我给大家整理了在『面试当中』经常会问到的一些面试题,比如说,
1.进程间通信的方式?	什么是进程,什么是进程间通信,有哪几种,原理是什么?
2.僵尸进程,孤儿进程
3.线程同步怎么解决? 什么是线程同步,怎么去解决它呢?
4.大端和小端的区别?	和网络相关
5.Io多路复用有哪些方式?区别?和特点
6.静态库跟共享库的制作以及使用它们的原理和区别
7.滑动窗口的机制
8. TCP三次握手,四次挥手
9.TCP和UDP的区别
...
1
2
3
4
5
6
7
8
9
10
参考书籍:

《Linux内核设计与实践》

//Linux网络编程相关的书籍,
《计算机网络第7版》
《TCP/IP详解卷1》
《UNIX网络编程第3版》
1
2
3
4
5
6
7
8
  • 很多同学在学习这些内容之中,他们并不知道我们要先学什么,再学啥。也不知道哪些是必须掌握的重点,很多同学对这个没有体系。为了让大家在短时间里面。快速的掌握Linux服务器开发相关的知识
  • 以及能够让大家在面试的过程中遇到这些问题的时候,能够很准确的去回答出来

所以,我给大家准备了这门课程。这个是大家学习这门课程的第2个原因。

# 2.课程内容

Linux高并发服务器开发

第1章Linux系统编程入门
	介绍开发环境、GCC、Linux相关的一些基本函数!
	让大家快速的掌握,如何在linux环境中进行开发
第2章Linux多进程开发
	介绍,进程操作,
	重点:进程间的通信方式!这是面试中常问的!
第3章Linux多线程开发
	介绍,线程的分离,啥的,
	重点:线程间的同步,这是面试中经常会问到的!
第4章Linux网络编程
	Linux网络编程相关的一些API,还有一些网络模型,协议啊。
	最重要的:TCP通信的原理,以及,最重要的就是IO多路复用等等。
第5章项目实战与总结
	利用上面4章的内容,开发一个Web服务器。
	介绍:Linux服务器开发的一些其他的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

本机写的代码,能够同步到远程Linux当中,提高开发效率

  • 25分钟40秒『VScode远程连接虚拟机』
  • 安装一些插件:
Chinese语言包,将英语变为中文
    
Remote Development	远程连接到服务器,安装后发现多了一个东西
1
2
3

# ✔️1.Linux开发(环境搭建)✔️

  • 重要,省略

# ✔️2-3.GCC

  • 整理到学术/工程

# ✔️4-9.静态库和动态库

  • 整理到学术/工程
- 04-静态库的制作
- 05-静态库的使用
- 06-动态库的制作和使用
- 07-动态库加载失败的原因
- 08-解决动态库加载失败的问题
- 09-静态库和动态库的对比
1
2
3
4
5
6

# ✔️10-12.Makefile

  • 整理到学术/工程
  • 『Redis?』

# ✔️13-16.GDB调试

# ✔️17-标准C库IO函数和Linux系统IO函数对比✅

  • 接下来,我们去学习一下Linux中的文件IO,也就是文件的输入、输出操作

那么在这儿呢首先呢给大家去解释一下这个名词啊,就是这个io啊。那很多同学呢可能啊,也学过这个标准c库的文件io啊,但是很多人呢不能够去理解什么是这个io啊。

那么文件输入和输出啊,那什么是文件输入?什么又是文件输出呢? 那这样呢我们要啊看站在哪个『角度』了啊!

  • 文件的角度

那比如说呢我们是站在这个文件的角度啊,比如说有一个文件,对吧? 那么对于文件来说

1,它的这个输入什么呀?是不是从外面把这个数据啊写到我这个文件里面,对吧?那这个外面是哪儿呢?其实就是我们的『内存』啊,这是我们的内存,这个叫做输入。 2,那输出呢输出就是从我们这个文件呢把数据呢给读取到内存里面,对吧?

  • 内存的角度

那么如果说我们站在这个内存的角度啊,比如说这是内存这是一个文件,对吧? 那站在内存的角度

1,什么是输入呢?是不是从文件里面把数据读取到内存里面,对吧?

2.那输出呢就是从内存里面呢把数据写到文件当中啊。

那你站在这个角度的不同呢,其实这个输入和输出它的这个呃理解呢也不一样,对吧?

  • 我们应该是站在哪个角度去看这个输入和输出呢?
  • 啊注意了,『应该是站在这个内存』的角度啊,因为我们是写的这个程序,对吧?

那程序呢它是在我们这个内存当中啊,就是我们运行程序的话呢会加载到内存里面,对吧? 它是在这个内存里面呢去运行的啊也就是加载到内存里面,对吧?

那么呃我们站在内存的角度去看的话,输 入呢就是从文件里面去读取数据到内存里面,对吧? 输入嘛输入到内存里面来嘛。 那么输出的话呢就是从内存里面呢把数据写到文件里面啊,就是输出啊。 所以这个io啊这两个单词啊大家一定要能够去理解啊! 什么是输入,什么是输出? 我们呢是站在这个内存的角度去看待的!

  • 好,那么接下来呢我们要去学习的是这个linux当中的文件io啊。

那在这儿呢我们呃,先来啊对这个linux系统当中的这个呃io啊,io操作啊,或者说呢就是这个文件io的一些函数啊,和同学们呢之前啊学过的这个标准c库的文件io啊,来做一个比较啊那同学们学过这个c语言,那肯定呢学过这个c语言当中这个标准的c库的io,那我们要对他们呢进行一个比较啊,我们得知道他们之间呢有什么地方是相同的,什么是不同的,对吧?

那么这儿呢有一张图啊,这张图呢就是标准的c库io函数啊相关的一些内容啊。 那么在这个左边呢大家来看一下,左边呢是我们常用的一些这个标准c库的啊这个io函数,比如说fopen去打开一个文件,对吧?

那这个呢是同学们呢呃之前已经学习过的这个标准c库的io函数啊。 那么呃首先呢他们这个『标准c库io函数啊』跟这个『linux系统的啊这个文件io函数啊』呃

  • 1、第一个区别呢就是在于标准c库的io函数呢它是可以跨平台的啊。 那什么叫跨平台啊什么叫跨平台呢?就是啊我们啊呃用这个标准c库这些函数啊开发的程序呢啊在各个平台上呢都能够运行。
  • 2、啊那什么是平台? 所谓平台呢就是不同的操作系统嘛,对吧? 那跨平台呢就是跨操作系统啊 比如说我开发了一个这个呃我用这个c函数库啊去开发了一个程序啊,那我可以在这个windows系统当中呢去运行啊, 也可以呢在这个linux系统啊,或者呢在这个mac系统当中去运行。
  • 3、那这个呢就是啊跨平台啊那么还有像大家学习的这个▁q t啊,他呢也是这个跨平台的啊。 那么这种跨平台它是怎么去实现的呢? 啊那么一般呢这个呃跨平台呢『它实现的方式呢无非就两种』啊, (1)那一种呢就是呃像这个java啊java那样,它有一个这个java虚拟机对吧? 那么。它为每一个这个平台呢都开发了一个这个加法虚拟机啊而这个加法程序呢哎你在任何平台上呢写的这个代码呢都是一样的啊 那么跑的时候呢它也是跑在这个加法虚拟机里面的啊那么不同的平台呢,这个加法虚拟机啊它的实现呢也是不同的,对吧? (2)那么呃这个我们像我们这个标准c库啊,其实我们呃可以称之为什么呢第3方库啊,就是标准c库啊我们可以称之为第三方库, 也就是说它不属于操作系统的啊它不属于操作系统。那么它的这个跨平台是怎么实现的呢? 那么它的这个跨平台呢,『其实呃它是在这个各个平台上啊调用了不同平台的这个呃『系统api』啊 比如说呢我们在这个windows平台上啊使用这个fopen打开一个文件,对吧?那么他呢会呃调用这个windows系统的这个api呢,去打开一个文件啊。 就是呃这个库呢它在封装的时候呢针对不同的这个平台啊,采用不同平台的这个api啊去实现。 那这个是呃给大家介绍的两种啊实现跨平台的这个方式啊。
  • 4、那么也就是说啊我们这个标准c库的io函数啊,它呢其实是呃要『高于』我们接下来要给大家介绍的这个『linux系统io函数』 那他们之间呢。是什么关系呢? 其实它们之间呢是调用和被调用的关系啊。 比如说呢我在这个linux的平台下,我调用了一个呃标准c库的这个fopen函数去打开一个文件,对吧? 那么他呢能够去打开文件,其实底层呢它会先调用这个linux啊系统的一个api,叫open函数啊,后面呢会给大家介绍啊。 我们说啊这个linux系统的io呢啊这个io函数或者ioapi啊,它呢是『更加偏底层的啊,或者呢说更加低级一些。』
  • 5、而这个标准c库啊它的这些io函数啊它是更加高级的啊。 那么高级呢呃『首先它的这个效率呢是要高的啊』那这个呢啊我们一会再说。
  • 6、啊那一般呢我们写这个程序啊,呃肯定呢是建议大家呢去使用这个标准c库的io函数啊,而不能说我们呃学习了这个linux的一个系统l函数, 那我们就去使用这个linux的这个系统IO函数啊,不是这 样的啊,我们要根据不同的这个场景呢去使用啊!!
  • 7、那我我刚才也说了,『这个标准c库啊它的这个io函数呢它的效率呢是要高的』,对吧?
  • 啊那为什么要高呢?
  • 因为它里面呢呃是带有这个缓冲区的啊缓冲区的。

那我们什么时候去使用这个linux系统io函数呢?啊其实它也是有这个使用场景的啊,你比如说这个网络通信的时候,对吧? 那这个呢是我们后面啊也会给大家介绍啊,给大家去学习这个呃linux当中的网络通信啊。那现在呢我们还没有学到啊,我们来看一下这个图啊。 那么这个图呢比如说左边呢是这个标准的一个c库的一个函数,是吧?它返回的是一个file文件指针,对吧?返回的是一个文件指针啊。那其实我们可以通过这个呃linux啊,它的自带的一个帮助文档啊。 man。 好,那么这个。『标准c库啊,它的api呢都是在这个第三章里面啊』 man 3啊然后呢呃fopen,大家来看一下这个apple open它的返回值是不是返回的是一个FILE * 啊,对吧返回的是一个发小新啊。

好,那么在这儿呢我们呃要搞清楚啊这个FILE *里面它里面呢都有哪些东西,对吧? 那么这个FILE文件指针里面,它其实呃就是一个结构体啊那这里面呢主要有呃三个部分比较重要的啊,需要大家知道的那一个呢是。 这个文件描述符啊文件描述符。 那注意了,这个文件描述符呢我们啊接下来呢啊要经常跟他去打交道啊,那它呢其实就是一个整数值,对吧? 那这个文件描述符是用来干嘛的呢? 它是用来指向一个已经打开的文件啊!! 比如说我们调用apple,对吧,我们去打开一个文件,那么 这个时候呢他就会在这个结构体里面就会有一个文件描述符这个整数值啊指向你这个文件啊指向你这个回的文件。 啊这个就是文件描述符它的一个作用啊,它是用来定位这个文件用的啊!!!!!

  • 那这个文件描述符呢呃在我们这个linux系统当中啊,我们一般称之为文件描述符啊
  • 在这个windows系统当中呢呃一般呢我们称之为这个文件句柄啊这个呢大家了解一下。 然后呢在这个FILE结构体当中啊。 第二个比较重要的就是这个文件的读写指针 那我们看这个名字也能够知道它是干嘛用的,,是用来读取数据和写数据的,对吧? 读取数据和写数据啊,,其实就是操作我们这个数据的啊, 其实在这个里面呢它维护了啊两个指针,用于你去写数据啊其实在这个里面呢 它维护了两个指针用于你去啊啊那么然后呢还有呃

第三块比较重要的就是这个io的一个缓冲区啊io的缓冲区。

那么呃这个io缓冲区它是干嘛用的呢?

  • 就说咱们啊所学习的『所有的啊标准的c库io函数呢,它们其实都是带缓冲区的啊』什么叫缓冲区呢? 呃比如说我们使用这个fwrite啊去往我们这个。文件里面去写数据,对吧? 那你写数据的时候啊,我们是我们可以去看一下这个呃帮助文档啊。 它有几个参数,第一个呢是呃一个指针,这是我们要写的一个数据,对吧? 第二个呢是size_t,这个呢是其实就是呃这个块的一个大小啊, 每个呃单个字节的一个就是单个块的一个大小,就是说『指前面这个数据单个块的一个大小』』 然后后面一个这个size_t啊然后nmeb,这个呢其实就是『每一个块的啊具体的一个这个数量,就是说我到底有多少个这个块,对吧?』 比如说我们往这个文件当中啊去呃写一个这个字符串,对吧? 那么字符串里面每一块其实就是一个字符嘛。 对吧? 其实它是占一个字节,对吧? 那么有多少个字节呢? 其实就是有多少个这个字符,对不对啊? 是这两个参数。

什么是缓冲区,缓冲区的作用 是不是就是提高我们这个啊执行的一个效率啊? 然后这个我们还要指定这个file*啊,这是文件指针嘛,对吧? 我们要通过这个文件指针去操作那个文件嘛,对吧? 在这个文件指针里面,它我们刚才说它维护了什么他维护了这个啊文件描述符啊,文件的这个读写指针,对吧? 文件描述符呢是找到这个文件用的,文文件读写指针呢是操作这个文件的数据的。 啊还有这个缓冲区啊。 那你比如说我们往这个里面呢去写一个数据,那你说它是一下子就把这个数据啊这个写到磁盘里面嘛?? 啊它不是这样的啊它不是这样的,它里面呢它会有一个缓冲区!!! 那你写的时候啊,比如说你写了多次啊写了多次,,把这个数据呢都写到这个缓冲区里面了啊然后呢。 呃他这个缓冲区啊如果说已经满了啊,他就会把这个数据啊调用一次我们这个linux的一个系统啊io函数啊, 把这个数据呢啊写一次写到这个磁盘里面啊写到磁盘里面,所以说这个就是缓冲区。

  • 那缓冲区的作用什么呢?
  • 大家想一想,缓冲区的作用是不是就是提高我们这个啊执行的一个效率啊? 对吧?执行我们这个程序的一个效率。

好,那大家想想啊,比如说啊我们呃要从一个地方把这个数据啊运动就是运到另一个地方,对吧? 那如果说你你每一次运的话,你写一次,对吧你运一次啊把一个数据过去啊运一次把一个数据过去啊。 然后呢还有一种方式就是我们有一个缓冲区啊,你把数据呢先写到这个缓冲区里面,然后再一次把这些数据放到这个磁盘里面,对吧? 因为我。我们知道是不是要操作这个磁盘啊,往磁盘里面去写数据吧,是不是要跟这个硬件打打交道了,对不对? 那么『果说呃你这个呃频繁地去操作这个硬件的话,那它的这个效率呢肯定是啊没有这个缓冲区要高的』 大家想一想是不是这样的,对吧啊那么在这儿呢,其实这个缓冲区啊,,它什么时候往我们这个磁盘里面写数据呢? 啊一个呢就是我们,,这个缓冲区里面的数据满了,那么它会往我们这个磁盘里面呢去写入数据。 啊那么还有呢我们可以通过强制调这个啊fflush,就是强制的去刷新你这个缓冲区,把你缓冲区里面的内容呢哎写到磁盘里面啊 或者呢我们呃比如说呢正常关闭啊,我们调用这个a,class对吧? of,close。 或者呢我们函数呢结束了,对吧? exit退出函数了啊。 会先把这个缓冲区里面的数据啊写到磁盘,对吧? 因为如果说你不写的话啊,你就把程序停掉了!!那么是不是有些数据就没有写到磁盘里面,是吧? 啊所以说他要做这样的一些操作啊。


  • 那么呃刚才也说了,这个缓冲区呢它能够啊提高我们这个效率啊那么提高的效率它主要是提高在哪啊?
  • 是不就是『降低了我们写磁盘的一个次数』,对吧? 原先呢我们写的话啊一次呢哎写到这个磁盘里面,你就要去啊写我们这个磁盘,对吧现在呢我有了缓冲区,对吧你就要去啊写我们这个磁盘对吧现在呢我有了缓冲区,对吧? 你就要去啊写我们这个磁盘,对吧现在呢我有了缓冲区,对吧? 你就要去啊写我们这个磁盘,对吧?

好,然后呢再到在这儿呢大家还要知道—— 就是呃关于我们这个缓冲区啊它的一个大小呢啊它默认呢是这个呃8192个字节啊,大概呢就是8K, 对吧大概就是八k啊那么当然了,这个是可以调节的啊,但是呢不建议大家去调节啊!!! 因为这个呢是一个理想的这个大小啊,,就是说呃这个开发人员啊他这个试过了大概呢是八k的时候呢,他的这个效率呢是最高的啊。 好了,那咱们总结一下啊就是说呃标准c库io函数啊它是呃带有这个缓冲区的,对吧?它呢能够提高这个呃效率啊。

而接下来l『『我们要学习的这个linux系统io函数呢,他们是没有这个缓冲区的啊』』,我们调用一次这个比如说呃red啊。 那么他就会写一次这个磁盘啊那调用一次这个read,他就会去读一次这个磁盘。 啊那这儿呢大家要啊了解啊,那了解了以后呢,我们就可以在合适的这个时机啊选择合适的这个函数啊。 那就比如说我刚才说的这个网络通信,对吧?

所以说呢在这个网络通信的时候呢,我们啊就应该使用这个呃linux系统的io函数啊, 那么在我们呃对这个磁盘进行读写的时候呢,哎那我们就需要有这个。 缓冲区,对吧? 提高我们这个效率啊就应该选什么选这个啊标准c库的含有函数,那这点呢大家要搞清楚。


  • 标准C库的IO是带缓冲区的,提高了效率:指的是,降低我们操作这个磁盘的效率。
  • 而我们要学的Linux的『系统IO函数』呢:他们是没有缓冲区的!!
  • 我们调用一次这个,比如说write,它 就会写一次这个磁盘!
  • 啊那这儿呢大家要啊了解啊,那了解了以后呢,我们就可以在合适的这个时机啊选择合适的这个函数啊。
  • 那就比如说我刚才说的这个网络通信,对吧?那网络通信呢它肯定是要要求这个呃效率的,你想想是不是啊?你跟这个别人进行一个网络通信肯定是要求这个效率的,对吧?
  • 那比如说呃你想想我给你发了这个十句话,如果说这个十句话还在这个缓冲区里面,那你说你着不着急啊
  • 对吧?肯定着急嘛是吧?那如果这个数据啊在缓冲区里面,说明这个数据还没有发出去,是吧?
  • 所以说呢在这个网络通信的时候呢,我们啊就应该使用这个linux系统的io函数啊,
  • 那么在我们呃对这个磁盘进行读写的时候呢,哎那我们就需要有这个缓冲区,对吧?提高我们这个效率。
  • 啊就应该选什么选这个啊标准c库的IO函数,那这点呢大家要搞清楚。

好了,那这节课呢我们就先介绍到这儿啊

缓冲文件系统
缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用,
- 当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读入接收的变量。
- 执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。

由此可以看出,内存 “缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器而定。
1
2
3
4
5
6

# 18-⭐️虚拟地址空间

嗯。好了,那上节课呢主要给大家去介绍一下标准c库io 函数。 和linux 系统i o 函数的区别啊,那我们再介绍标准c q y o 函数的时候呢,我们说啊这些标准c库i o 函数啊。 他是如何去操作磁盘当中的文件呢?啊,其实就是通过一个叫file 的文件指针去进行一个操作,对吧?那你比如说呢我们。通过这个fopen去打开一个磁盘当中的文件。那么这个函数呢它会返回一个这个返回值啊,这个返回值呢就是一个file 类型的文件指征。 啊,那么通过这个文件指针呢,我们就可以去操作

  • 磁盘当中的文件。那你比如说啊我们在后面呢调用这个fwrite 啊。 那么这个函数包括这个fr 的对吧,等等这些函数里面是不是都需要传递一个这个file 新的文件指针啊,对吧?那这些函数呢其实就是通过这个文件指征。操作磁盘当中的这个文件啊,那么在这样呢我们呃还需要知道的就是这个file 文件指针当中啊,他其实就是一个结构体,对吧?

感觉是复习

  • 那它里面呢有几个比较重要的部分啊,我们需要知道的一个呢是这个文件描述符啊,那他呢是一个整形值,它是用来定位我们这个文件用的。那这个文件描述符呢,它会指向啊我们需要操作的这个啊磁盘文件啊。那么还有一个呢是这个文件读写指针。那他呢是啊,对我们这个文件当中的这个数据进行一个操作的,对吧?通过这个文件啊读写指针去进行一个操作啊,那它里面呢有这个。读指针还有这个写指针,对吧?然后呢还有一块呢就是这个缓冲区啊,那这个缓冲区呢它主要是用来提高这个效率的,对不对?

  • 那么其实呢我们也可以通过这个代码去看一看这个file 文件指针啊。那在这呢我们打开v s q 的啊,然后呢我们在这里面呢。

  • 随便呢去新建一个啊test 点c 啊,随便新建一个这个文件哈。 那么在这样呢,我们呃。 先引入一个s d。 啊,i o 点h 然后呢写一个泛函数。 不测零。 好,然后呢我们直接啊去输入这个file。 啊,全部都是大写,对吧?好,那么它是一个结构体啊,我们就直接写一下,然后按住肯鞘腱鼠标呢,你上来变成一个小手,对吧?点进去。 好,那么我们来看一下它是在哪呀?他是在这个,你看是不是在叉八六杠六十四linux gnu 啊beats。 taes, 然后里面呢有一个头文件叫yr 点h 是吧?也就是说它是定义在这个三号点h 头文件里面的啊,那我们来看一下。 在这里面呢,他只做了一件事情啊,就是重新给他起了一个这个定义了一个类型,对吧?啊,就是呃file 这是新的类型,然后。 男生的类型呢叫下划线i o 下划线画哦,然后呢它是一个r 的结构体啊,然后我们去看一下这个真正的啊这个file。 好,那它是一个什么呀,结构体吧,对不对?那么在这里面呢,我们看一下它里面的内容啊,比如说照呢有一个岔心对吧?这些都是查新,都是指针吧。 有什么呢?有read p t r,read and read base, 还有right base,right pd 啊啊right end 啊,还有这个。 八核base buffer end 啊等等,对吧?那么这个read p t r 呢其实就是我们之前所说的这个读指针啊,right p d r 呢就是。 啊,斜指着啊,然后这个八五base 呢,其实就是我们这个缓冲区啊,它的一个起始啊,及时的一个指针起始的位置。然后这个buff n 的呢。 是这个缓冲区的一个结束的一个位置啊,我们说这个file 结构体里面对吧?这个文件指针里面它是不是有一个缓冲区啊,对吧?最后缓冲区呢啊他其实就是通过这个指针来进行一个维护的。 有一个起始的,有一个结束的对吧?好,那这个是啊,我们说有缓冲区,还有这个读写指针,对吧?然后我们再往下看啊,还有一个什么呢? 还有一个叫in 的类型的file number 啊,这个方向number 呢其实就是啊我们之前说的这个文件描述符啊文件描述符。 那么这个文件描述符它是怎么来的呢? 这个文件描述符啊,其实啊我们说这个。 标准西裤的io 函数啊,他在调用的时候是不是首先会去调用我们这个啊linux 系统的i o 函数啊,对吧?那。 系统的i o 函数呢它会返回啊,我们后面会看到啊,它会返回一个这个文件描述符啊,就是这个fd 啊它的一个这个标识。 啊,那然后呢,我们这个标准西裤的啊i o 函数呢得到了这个文件描述符以后呢,唉再给这个i number 进行一个赋值,对吧? 附上你这个啊系统调用啊,就是这个linux 系统调用返回的那个文件描述符的值复制给他啊。所以说呢,我们说啊这个标准。西裤。 啊,io 当中啊,它都是通过发小新啊,就是一个文件指征,对于文件呢进行一个操作对吧?那么他怎么去找到这个啊文件,对吧? 是通过这个啊fd 文件描述符啊,它的一个这个值啊,去找到我们对应的这个文件定位。我们文件啊。 那怎么操作呢?就是通过这些啊读指针写指针啊,还有这个缓冲区对我们的数据呢进行一个操作啊。好,那这个呢大家也要知道一下。 好,那么接下来呢我们给大家去介绍第二个概念。 啊,那么这个概念呢叫做。 虚拟地址空间。 啊,虚拟地址空间。 那咱们呢先把第二个概念啊介绍完了以后啊,我再给大家去介绍这个文件描述符。 那么这个虚拟地址空间啊,首先问大家啊,这个虚拟地址空间它存不存在? 他存不存在呢啊。 那比如说我说啊呃有一个虚拟的世界,那你说这个世界它存不存在了,呃,虚拟的世界肯定是不存在的嘛,对吧?那所以说呢这个虚拟地址空间了。 他也是不存在的啊,那他呢是我们程序员啊想象出来的啊想象出来的。那么这个虚拟地址空间啊。 他是干嘛用的啊,那在这样呢呃简单的给大家去说一下啊啊好,让大家呢去呃理解啊,为什么要有这个虚拟地址空间。 啊,那举个例子啊,比如说我们啊有一个电脑对吧?那他呢有这个四g 的内存。 啊,比如说我画四个格子,一个格子呢就是呃一个g 对吧?那比如说我有一些应用程序啊,就是说我有三个啊,第一个应用程序呢占一g。 啊,第二个应用程序啊,它应用这个启动起来以的话占两g 啊,然后呢还有一个呢是蘸这个两g。 那比如说啊我现在呢有一个程序一g 的应用程序启动起来了,是不是占了一寄了。然后第二个它是不是占了两g 是不是呃占了中间这个。 两g 对吧?好,然后呢,你看现在是不是总共是四g 嘛? 现在已经占了三g 了,对吧?那我还要一个应用程序是占两g 它能够运行吗?他不能运行,因为你只剩遗迹了,对吧?啊,如果说。 按照我们这个正常的想法,它是这样去运行的嘛,对吧?加载到内存里面嘛。好,那么现在这是一个问题,就是说我总共就四g 的内存啊。 你现在有这么几个应用程序啊,他要占用的内存呢都呃比较大,对吧?一g 两g 两g 那肯定是不能都给它加载进来。那现在有个问题啊。 这是第一个问题啊,那么接下来再来看,比如说我一g 的内存啊,这个释放了,是不是现在我有呃两g 的空余啦,就是说呃前面一句和后面一级对吧? 中间的两g 是不是还是占用着呢?那么这个时候我另外一个两g 的应用程序能不能加载进来呢? 能肯定是能对吧?但是有个问题,什么问题呢?就是说你现在加载的话,你是不是呃前面这个加在一起,后面再加载一件,那它的这个内存地址是不是就断掉了? 对吧就断掉了啊。好,那么这是我们刚才说的啊两个问题。其实还有一些其他的问题啊啊那么你你想一想,如果说我们用这种方式去。 啊,加载这个数据的话,加载啊程序到内存里面的话,是不是有这么一些问题啊,对吧?好,那为了解决这个问题呢啊其实这个啊一些这个研究人员啊。 啊,他就发明出了什么呢?哎,虚拟地址空间啊,就是虚拟内存的这么一技术啊,那么虚拟内存呢其实就是它会有一个虚拟地址空间。 那么这个虚拟地址空间呢,它是不存在的啊不存在的那它是怎么得到这个虚拟地址空间呢? 那么首先哈呃咱们呢要有一个可执行程序啊,我们要把这个可执行程序啊运行起来啊。那么在这个可执行程序运行期间了。 啊,那么其实他就会对应一个虚拟地址空间啊,那么如果说这个程序啊运行结束了啊,那么这个虚拟地址空间它就不存在了。 啊,就不存在了,明白吧?那么其实这个程序啊运行起来之后啊,它其实就是一个进。 啊,经常呢我们后面要啊给大家去介绍的啊,那么呃其实进展呢就是啊我们系统啊给这个程序分配资源的一个这个最小单位。 啊,就是我们程序要运行嘛,对吧?那我们系统呢就啊创建一个进程啊,给他分配一些资源,对吧?让我们这个程序呢去运行啊,这个就是进程啊。 那么其实这个地方呢大家要搞清楚啊这个进程啊和程序的一个区别。那所谓程序呢它是什么呀?所以程序它就是啊我们这个磁盘上的代码。 他只是在这个磁盘上啊,那么进程或者说运行中的程序啊,他会加载到内存里面对吧?那么这个呢是叫做进程,它是呃我们运行这个这个程序。 你看吧啊,这个两个不一样。 那么这个虚拟地址空间了啊,他其实就可以用来解决啊我刚才所说的啊,就是我们这个把这个应用程序啊加载到内存里面。 啊,可能会产生的一些问题,对吧?可能会呃有一些的这个问题啊,那么当然了我们重点呢不是说去研究啊这个虚拟啊、内存啊。 就是他的一个技术是怎么实现的啊,咱们呢只需要理解这个虚拟地址空间里面的一部分内容就ok 了,明白吧? 那理解这部分内容呢,主要是为了我们后面呢啊学习。比如说这个进程对吧?比如我创建了一个这个进程,我又创建了一个这个紫禁城啊,那么它里面的这个啊内存啊。 啊,是如何变化的,对吧?啊,比如说这个占空间啊或者说非空间它会被复制啊,对吧?是为了让我们去解释这些东西的啊,理解我们这个应用程序。 他执行的一些啊东西的,明白吧?所以说在这样呢,大家也不要去觉得啊这个虚拟地址空间啊啊不重要,或者说呢啊比较难学啊,我们所需要了解的东西呢。 啊,非常少啊。好,那呃回到这个虚拟地址空间啊,我们继续来说啊,我刚才说呢这个一个应用程序啊运行起来以后呢,它就对应了一个进程,对吧? 那么这个进程呢其实就会有一个虚拟地址空间啊是虚拟出来的,明白吧?那么这个虚拟地址空间它有多大呢?那这个呢是由电脑的c p u 啊来决定的。 啊,那比如说这个三十二位的计算机啊,那他的这个呃虚拟地址空间的大小呢就是二的三十二次方啊。那么大概呢就是四个g。 啊,那么六十四g 的这个六十四位的机器呢啊可能同学会这样去想啊,就是二的六十四次方啊,其实不是啊,一般呢他是这个二的四十八次方。 啊,那这个呢就非常大了。 那样吧。那么在这呢,我们就以这个三十二位的机器来说了,明白吧?我们不以这个六十四位的去说啊,我们就以三十二位的这个机器来理解啊。 那么所以说呢,你要知道有这么一个模型啊,就是进程啊运行起来以后啊,他需要对应这样的一个模型来解释一些东西啊,就是什么呢? 比如说同学妈妈都听过的这个站,对吧?哎,对对不对等等啊,它是用来解释这些内容的啊。好,那咱们来看一下这个虚拟地址空间啊。 那么这个虚拟地址空间呢,它分为了两部分啊,一部分呢是这个零到三g 啊,我们说三十二位的机器嘛,对吧?它是这个虚拟地址,空间呢是四g 啊。 然后一个是零到三g 一个是三g 到四g 这个里面是一g 对吧?然后下面是三g 啊,那么零到三g 呢它是这个用户去。 啊,用户去,然后。 这个还有一件呢是这个内核区啊内核区。那么呃这个虚拟地址空间里面的这个数据啊,他会被什么?他其实最终啊会被我们的。 这个cpu 当中的一个啊这个逻辑单元啊,逻辑管理单元叫做这个mm u 啊,m u 就是内存管理单元啊,比这个东西。 然后呢,被他干嘛呢?映射到我们的真实的一个物理内存上看看吧,就是说这个虚拟地址空间是为了方便我们啊,一个是解决我们刚才说的那些问题。 对吧啊就是这个内存加载的一些问题啊,一个呢是解决我们说这个啊这个站啊堆啊啊解释这些概念呢,对吧?解释这些模型的。 好好,那我们说啊这个虚拟地址空间啊,它最终呢会被啊我们这个c p u 里面的一个逻辑管理单元叫做mm u。 啊,m m u. 给他映射到我们这个内存啊,真实的物理内存上,对吧?那么呃大家想一想这个mm u 啊就是这个c p u 里面的一个这个。 程序员对吧?或者说一个管理东西啊管理器它是不是就应该啊能够将我们这个虚拟地址啊和我们这个物理地址之间进行一个转换。 对吧啊就是我们所说的这个虚拟内存管理啊这一个技术啊,那么我们一般说的时候呢,我们不是拿这个呃真实的物理内存来说了啊,我们一般呢都是拿这个虚拟地址空间去说。 啊,就是你知道啊这个虚拟地址空间里面的数据啊,它最终呢会全部映射到实际的物理内存当中啊,它被水印什么就是被这个cpu 去映射。 啊,那么呃当然了,我们说这个虚拟内存地址啊,就是虚拟地址空间,它占四g 对吧?四g 的空间啊,那这个呢只是一个理想的状态。 啊,它最多呢只能加自己啊,实际上呢他占用这个物理内存有四g 嘛啊其实不会啊,实际上它是不会占用这个啊物理内存四g 的,明白吧? 啊,那这个呢都是由这个cpu 去管理的啊,咱们呢不用去啊重点的关注啊。 那么咱们再来看这两个区啊啊首先呢说这个零到三g 它呢是用户区啊,那用户区呢就是啊说可以让这个用户啊去自己操作。 好啊,那咱们呢作为一个普通的用户啊,你可以操作里面的这个啊东西啊,比如说我们运行一个应用程序啊,那么就可以操作这个用户区里面的某些空间。 啊,你比如说这个堆空间。 对吧啊你比如说这个占空间是吧,你都可以去进行一个操作啊。 然后呢,再往上的话呢,就是这个呃内核了啊,那么内核呢它是啊我们这个普通用户啊是不能进行一个操作的。 啊,我们是没有这个权限去操作的啊,那我们来看一下这个用户区域啊有哪些东西啊。啊第一个呢比如说受保护的地址啊,一般呢是这个零到四k。 啊,那像我们这个now。 那大家都知道空指针对吧?还有这个number one point inter。 啊,那我pt 啊,那么像这些呢,其实他都是在这个地址里面啊,受保护的地址啊。然后呢,还有像这个。 text 字段了,这个呢是代码段啊,所谓代码的呢就是我们程序啊是不是要加载到内存当中啊,那加载的话是加载的这个代码啊代码的这个数据。 啊,都是这些二进制的机器指令啊,然后呢像这个date 对吧?它里面呢是以初始化的全局变量啊,l o b s s 段。 它里面呢是未初始化的全局变量啊,比如说我们呃这个定义了一个全局变量啊,然后没有初始化,那么他会放在这个里面啊,包括你直接呢付一个零,它也是没有初始化,也是放在这个里面了。 啊,然后这个堆对吧?堆空间啊对于大家都知道吧,我们可以通过这个new 或者mallock 创建出来是吧?创新出来的这个数据都是在堆里面进行管理的。 啊,然后还有这个共享库啊,那共享库呢其实就是啊加载我们之前所学习的这个啊共享库,它里面的这个代码在在在这个里面啊,然后还有这个。 占空间啊,那么一般的这个堆空间呢,它是要比占空间要大的,对吧?这个我们也知道啊,占空间呢他一般呢啊都比较小是吧?好,然后呢大家再来看这个箭头啊。 啊,对,空间呢它是从下往上,也就是说从dd 值往高地址去存啊,就是说你有数据的话,它是先存帐,然后再往上去存的。 而这个占空间呢,它是从这个上往下去层啊从上往下去走,就是说高地址往低脂低地址去测是吧?那么占空间里面是不是像这些。 不变量啊,对吧?都是保存在栈空间里面啊。 然后呢,还有这个命令行参数,这个大家应该都好理解是吧?命令行参数我们这个是不是呃比如说我们执行这个程序是不是可以传递这个参数进来。 对吧,这个就是命令行参数放在这里面,还有一些这个环境变量啊啊我们这个每个用户啊,linux 每个用户啊其实都会有一些这个环境赔偿。比如说我们输入nv。 啊,那么他会把这些环境变量的这个数据啊放到哪呢?放到这个啊这一块里面。 明白吧,啊,好,那这是用户区里面的一些内容啊。当然了,这个用户区里面呢还有一些其他的内容啊,我们呢只需要关心这几个啊比较常见的就ok 了。 啊,然后呢再来说这个linux 这个内核区啊,那我刚才说到这个内核区里面的这个数据啊,我们是没有权限去操作的。 普通用户是没有权限去操作的啊,那么内核区的数据啊对于咱们一个普通用户来说啊,既没有这个读权限,也没有写权限。 啊,那如果说我们想要操作这个内核里面的这个数据,我们应该怎么办呢?那我们啊就必须要进行这个系统调用啊,那所谓的系统调用呢就是。 啊,调用linux 系统的api 就是调用linux 系统的api 这个叫系统调用,明白吧?我们可以通过这个系统调用。 通过系统api 函数来完成某些操作。啊,你比如说啊我们linux 里面有这个read 这个io。 法律的rise 是吧?我们可以通过这个系统调用啊,对于我们这个内核里面的数据啊去进行一个操作。比如说呢我们通过read 呢去读取。 当中的这个数据对吧?或者说呢通过write 呢去写数据啊。 那么内核区他都有有些啥呢?啊,比如说呢这个内核区呢它可以进行一个内存管理啊,还可以进行进程管理,对吧?还可以。 啊,像这个设备驱动管理啊,所谓设备驱动管理呢,比如说这个网卡驱动对吧?显卡驱动啊也是在这个内核当中进行一个管理的。好,然后呢还有这个。 啊,o v f s 虚拟文件系统对吧?那每一个这个操作系统是不是都会有文件系统啊,对吧?linux 它也会有啊。 好,那么到这呢啊就给大家呢介绍了这个虚拟地址空间啊,那咱们呢总结一下啊,那首先呢说这个虚拟地址空间啊。 他是不存在的,对吧?他只是为了让我们去解决这个啊程序啊,加载内存啊啊他的一些问题啊,以及呢。 啊,解释一些我们这个编程过程当中啊所需要用到的这些模型。比如说站啊,对呀对吧?它是用来做这样的事情的啊,然后呢零到三g 呢使用户区。 啊,我们可以直接对它进行操作,那么三次三g 到四g 呢是内核区,我们是不能直接去操作的。那我们要想去呃访问里面的数据啊,我们应该通过这个。 调用系统api 对吧?就是执行这个系统调用去进行一个操作啊。好,那么呃到账呢就给大家介绍了这个虚拟地址空间啊,我们后续呢。 还会再去给大家介绍这个虚拟地址空间里面的一些其他的内容啊。那这节课呢我们就先介绍到这啊,我们下一节课呢再见。

# 19-⭐️文件描述符

那你比如说呢我们写的这个test 点c 啊,它其实就是一个程序,对吧?它是程序的源代码啊,那么程序呢它是呃不占用这个内存空间的。 啊,他只占用这个磁盘的空间啊,它是稳健。那再比如说这个test,这个是可执行程序,对吧?那他也是一个稳健啊可执行文件。 那他呢也是只占用我们磁盘的空间,它不占用内存的这个空间啊。 那么什么是进程呢?就是当我们程序啊要运行起来的时候啊,比如说这个test 这个可执行程序,对吧?我们要把它给启动起来啊,那么你要去运行一个程序啊。 那操作系统呢应该会为这个程序呢啊这个它的一个启动对吧?或者说他的运行分配一些资源啊,那这时候呢我们这个操作系统呢他就会去创建一个。 进程啊,那么这个呃进程呢其实就是啊给我们这个运行的程序,它所分配的一些资源呢啊一个这个东西啊。 那么呃这个进程啊其实就是正在运行的程序嘛,对吧?那进程它是占用这个呃我们内存的一个空间的,对不对? 我们之前说啊啊这个一个进程启动以后啊,其实就是一个程序启动以后他会有一个虚拟地址空间,对吧?那么这个虚拟地址空间呢他会通过这个cpu 当中的啊mu 这个罗。 级单元啊逻辑管理端妍把我们的这个虚拟地址空间当中的数据映射到哪儿去,映射到这个啊物理内存当中去。 对吧啊那么这个呢是啊这个程序啊和这个进程他们之间的一个区别啊,大家呢一定要搞清楚。 那么比如说呢我现在呢通过这个test 点c 啊生成了一个test 啊这样的一个可执行程序啊可执行文件。那么在这个test 这个可执行文件里面呢。 我们比如说呢做了一个读文件的操作啊啊,或者说呢做了一个这个啊写文件的操作啊。比如说呢我在这儿呢去创建一个。 个比如说。 a a 点或者说english。 啊,点txt。 可以吧。 啊,有一个english 点txt 啊,它是里面呢有一些数据。那比如说呢我们这个test 这个可行性程序啊运行起来以后啊,那么操作系统呢会呃生成一个进程。 啊,比如说这个进程呢叫做a 啊a 进程。那么这个进程呢唉他就会去读取或者说呢去啊写这个数据往哪去写呢?往这个english。 txt 里面去写啊,或者呢从这个里面去读取数据,对吧?那么我们啊如果说用这个标准c 啊,它的一个i o 函数库。 我们应该怎么去操作,所以首先呢fo come 对吧?di opp 打开这个文件啊,然后呢调用f read 去读取数据或者呢fright 去写数据。 对不对? 那么现在呢啊问大家一个问题,就是说呃为什么在这个程序当中啊,我们通过fo 碰以后啊,调用f read 或者fright,他就能够去找。 找到磁盘上的这个english 点txt 啊,对它进行操作呢。 啊,那这个我们之前说过是因为什么呀?因为它里面有一个文件描述符,对吧?我们说你调用这个open 啊ifo 本函数,它会返回一个啊file 文件指征。 这个文件指针里面呢,他就封装了一个啊这个内容啊,就是这个文件描述符它是用来去定位啊,你要操作的这个文件的,对吧?

  • 那么这个文件描述符啊。他在什么地方呢?
  • 啊,注意了这个文件描述符呢,其实他是在我们的这个进程的内核区啊。
  • 好,那我们来看这个图啊。他其实呢是在我们这个进程的啊,它有一个这个内核区,对吧?我们说呃0-3G 呢是用户空间啊,用户区,然后三g 呢到四g 呢是这个内核区啊。
  • 那么这个文件描述符啊,它是保存在这个就是啊内核区的啊,你回去的有什么管理呢?有内核进行一个管理。啊,那么内核管理呢呃就是在这个进程管理这这一块啊,有一个呃PCB 啊,我们之前啊就是说这个内核区啊,它有一个内存管理,对吧?那其实就是在这个内存管理这个模块里面呢,哎它有一个模块叫做pcb 啊,pcb pcd 呢就是process control pro。 对吧啊进程控制块啊进程控制块啊,由他呢去管理这些啊,管理这个文件描述符。
  • 那么这个比较抽象啊,就是说你怎么理解呢???
  • 那你想啊呃,『『我们内核呢他其实就是一个程序,对不对?其实任何就是一个程序啊,我们所说的内核呢其实就是一个程序。那么内核他既然是一个程序啊,呃那比如说呢我们这个啊linux 内核它是由谁开发的,是不是有那个呃,李纳斯托瓦斯?
  • 他去开发的对吧?他用业余时间呢去写了一个linux 内核其实这个linux 那个就是一个程序,对吧?那为什么后来叫做这个操作系统呢?对吧?linux 操作系统的。
  • 『『其实就是说我们光有内核呢还不行!!!对吧?那操作系统它是什么样?操作系统呢,首先它能够对这个呃整个系统运行呢进行一个后台管理,对吧?那再有一个呢,你这个操作系统呢呃上面呢得有这个工具啊。比如说我们平常用的这个office,对吧?啊,比如说我们呃之前用过的这个gcc 啊编译器,对吧?啊,还有我们之前啊学过的这个GDB 啊调试工具。 不对,你得有这个工具啊。好,那么这样呢才形成了一个这个啊这个操作系统啊,那么内核呢它其实就是一个程序啊。
  • 那呃那么这个PCB 啊其实就是一个啊非常复杂的结构体哈。那他是这个进程控制块啊。就是说PCB 呢它就是进程控制块啊,所以呢他要管理很多的这个东西,对吧?你想想我要去管理一个进程啊,那你是不是应该有一个东西啊去保存???啊,你需要管理的这些数据,对吧?啊,所以呢这个p d p 晋升这个进程控制块呢,其实就是啊一个非常复杂的结构体啊,那么我们可以把需要管理的数据啊都封装到了这个。 pcd 啊这个『『结构体里面啊,那当然了,这个不是我们重点要研究的东西啊。那么你看到这个pcb 以后呢,你要知道它是什么就行了。
  • 啊,说的就是这个进程控制块。
  • 『『PCB这个结构体』』』那么在这个结构体里面呢,它其实有一个数组啊,那这个数组呢我们称之为什么呢?称之为文件描述符啊,我把这个放大。『『看PPT的右边』』
  • 那么在这个PCB 啊,这个呃你把它理解为是一个很复杂的一个这个结构体,对吧?那么在这个结构体里面呢,它有一个文件描述符表!!啊,文件描述符表。那我们说了这个文件描述符表呢,它是一个数组
  • 啊,数组文件描述符表。那『『大家想一想,为什么要用这个数组来存储这个文件描述符呢?』』
  • 啊,文件描述符其实就是用来定位我们这个磁盘上的某一个文件的,对不对?那么在这个PCB里面啊,它有一个这个文件描述符表。
  • 是数组啊,那为什么他要用数组来存储这个很多个文件描述符呢?对吧?
  • 也就是说他是一个数组,它里面呢,可以存储n 个文件描述符啊,那每一个文件描述符呢都可以定位一个文件,是这样的吧,对不对?好,那为什么要有一个这样的一个表呢?啊,那你想啊我一个应用程序啊我一个应用程序启动了以后啊,那么你在这个应用程序里面啊,你比如说你打开了一个文件a。 是吧,那你能不能再打开其他的文件可以吧,对不对?那你比如说你打开了一个文件a 各位打开了一个文件b 又打开了一个文件c 啊,那你同时打开了这个十个。啊,这个文件是吧,那是不是就应该有十个啊,这个文件描述符分别对应或者分别定位到这十个文件?大家想一想是不是这样的,对不对?那么你一个进程你要管理这些数据啊,你是不是要有一个数组来存储这些文件描述符啊?对不对啊,所以说为什么在这个pcb 啊进程控制块里面,他要用一个数组来存储这些文件描述符啊,就是说我们一个进程呢它可以同时啊啊打开多个这个文件啊,所以所以说呢他要用一个数组,就是这个文件描述符表呢来存储啊。
  • 好,那现在呢大家应该能理解了这个啊文件描述符表为什么是一个数组。 对吧?
  • 好了,那么。 下来呢呃**再来说啊,这个文件描述符表就是这个数组啊,它的一个大小是多少呢?大小呢默认是1024啊,**默认的大小呢是1024就是说他的这个数组啊,这个默认大小呢是一零二四啊,那么你想一想每一个进程里边它是不是都有一个这样的进程控制块,对吧?啊,也就是说每个进程里面呢都有一个这样的文件描述符表啊,那你想每一个进程它默认能同时打开的文件的个数是多少,是不是就是1024啊,对吧?因为他这个。数组它的最大的一个大小啊,默认大小就是一零二四。所以说你最大能打开啊,同时能打开的这个文件的个数就是一零二四。

# 『文件描述符』

  • 好,那这个呢大家要了解一下,然后呢我们再来看一下啊呃就是说呃这个文件描述符表当中啊,前三个他目瞪呢是被占用的,默认是被使用的
  • 啊,那默认被谁使用呢?
  • 就是标准输出,
  • 标准输入,
  • 还有这个标准错误啊,标准输入标准输出还有标准错误。

记忆:水管,开始in是0

  • 啊,其实就是我们之前啊学的这个s d d e 啊,s d dout, 还有这个s d d error,对吧?这几个啊标准输入输出,还有这个标准输出。一个错误啊,而且呢呃这几个这三个文件描述符表啊,就是这三个文件描述符啊默认是打开的一个状态。
  • 啊,他们默认是一个打开的状态啊,那呃。。
  • 那我问了这个标准输出标准输入还有这个标准错误,它都是和谁绑定的?都是和谁绑定的,
  • 其实呢他们都对应了一个文件啊,那么他们对应的文件是谁呢?
  • 1、其实就是当前的终端,比如说我标准输出,他是不是输出到我们这个。啊,终端当中啊,对吧?啊,那么这个比较抽象啊,为什么比较抽象的就是说呃我们为什么啊这个文件描述符。啊,它指向的是当前终端啊,我们之前不是说这个文件描述符,它指向的是一个文件嘛,对吧?他定位的是这个文件啊。
  • 那你想一想啊,在这个linux 当中啊。他是不是有一句话叫做一切皆文件?对吧,一切皆文件。那么在linux 系统当中啊,不管是什么样的这个硬件设备啊,它最终呢都会虚拟成一个文件。通过这个文件呢去对它进行一个管理。
  • 啊,那你比如说咱们的这个**显示器对吧?网卡啊、显卡都会被虚拟成这个啊文件啊。**那么通过这个文件呢去管理这些设备。啊,其实就是这个设备文件对不对啊,那么
  • 『『这个标准输出标准输入,还有这个啊标准错误,其实呢就是指向了当前的这个终端啊。他指向的是一个设备文件啊,』
  • 那这三个大家一定要搞清楚啊啊他们分别呢是零一二啊啊这个他的红纸呢就是s d d e f a number。s d dout 发扬number,还有这个s d da l 发扬number。这三个呢大家要记好了,他默认是打开的,对应于我们这个当前的一个终端啊。那么呃这个标准输入啊输出错误啊,刚才也说了,他们分别对应的这个值啊是。0,1,2,零一二啊就是呃固定的啊不可能发生改变的,明白吧,啊,那么呃我们啊通过这个0,1,3呢就能够找到他所绑定的一个啊设备终端。
  • 啊,还有呢就是这个0,1,2呢,他们对应的终端呢是同一个!!就是我当前使用的这个终端啊,比如说呢我们在这个呃地方啊。
  • 『『『比如说我们去运行这个test,对吧?那呃他的这个标准输入输出,还有这个标准错误啊,就对于我当前这个终端,那我再打开一个,是不是就是对应另外一个这个终端啊。『HACV,觉得有理由,可以考虑,我每次点击一个程序,就弹出一个黑色的框框,2333,这下真相了。。』

# 『同一文件,不同文件描述符』

  • 对不对啊?好,那再回过来看啊。那么呃我刚才说了,这三个文件描述符对应的是同一个终端是吧,那也就是说啊这个不同的文件描述符它可以对应啊同一个文件啊,也就是说啊什么意思呢?比如说我一个文件啊,比如说a 点txt。我可不可以调用这个fopen 放函数去打开一次可以吧**。那我能不能再调用一次open o ifo 版函数,再去打开也可以**,对吧?好,那我还可以再调一次。再去打开,我可以调用多次。那么你多次调用的话,这个fopen返回的这个FILE*啊,就是一个file 文件指针它里面。 这个指向的这个文件描述符就是定位的文件描述符,它的值一不一样的啊,**注意了是不一样的是不一样的啊,**明白吧?所以呢我们这个一个文件呢可以同时被打开多次。那么打开多次呢他。
  • 这个文件描述符呢是不一样的。比如说我第一个呢打开我往里面去写数据。第二个打开以后呢,我往里面这个从里面读数据是不是啊?
  • 『『那么我们什么时候这个呃,这个文件描述符啊释放掉呢,就是我们去使用这个啊fclose 或者说呢我们后面使用这个linux他的一个啊系统i o 函数啊叫close 啊,去关闭这个文件描述符的时候呢,』』这个文件描述符呢他就会被释放啊,
  • 那被释放了以后呢。是不是就可以重新去使用了,对吧?可以重用这个文件描述服务了啊。那么如果说文件描述符啊他被占用的话呢,哎那它需要到这个文件描述符里面。就是表里面去找啊,找什么呢?找一个最小的啊,没有被占用的这个文件描述符去使用啊
  • 那么可能有的同学呢会有疑问,就是说呃。那我怎么知道这个呃,找最小的这个文件描述符,对不对啊,那这个呢我们不需要去关心为什么呢?因为这个是由内核来维护的。对不对啊,这个是由内核来维护的啊,我只是给你描述一下这个内核的行为啊。举个例子,比如说呢。我们啊呃现在呢前三个这个文件描述符啊是不是都被占用了,对吧?零一二啊,那比如说呢我再去打开一个文件,a 点txt。 那么这个时候他会找最小的没有被占用的。三是不是给你返回啊,对吧?啊,然后呢,比如说我又打开了一个b 点t s t。 那么他会找谁?他会找最小的没有被占用的四啊,指向这个文件啊。然后呢,比如说我又打开一个a 点t s t,那你又打开了同一个这个文件。 他是不是啊,同样的还会再去返回一个新的文件描述符,对不对啊,指向他啊,然后等等等,你可以往下啊一次去使用。 那么现在如果说我这个a 点tfd 第一个啊打开的我释放掉了,就这个文件描述符表,就是这个文件描述符三啊,我释放掉了。释放讲了以后是不是就可以。 重用呀,对吧?好,那比如说我下面再打一个一个这个c 点tsc,那么他应该使用的是什么?使用的是最小的,没有被占用的,是不是三啊,对不对啊? 么这个是这个内核把我们去完成的啊,内核的行为就是这样的啊。好了,那么关于这个文件描述符呢啊咱们理解那么多就ok 了。

# 『获得文件描述符』

  • 啊,那么我们怎么去得到一个文件描述符呢?啊,那我们后面呢啊要给大家去介绍这个linux 的啊系统api 啊,系统i o 的这个api 啊。 这个open好,open 这个系统函数。那么通过他呢就可以去得到一个文件描述符啊,那么得**到了这个文件描述符有什么用呢?我们是不是就可以操作这个文件描述符?**所对应的那个文件对吧?
  • 也就是说我们后面呢,比如说我们要去读数据啊,用的这个read 啊,或者写数据用的这个write 啊,那么这个里面呢呃我们在调用的时候呢,就不是传递啊。这个像啊标准c 库的这个啊IO 对吧?它的一个api 了传递一个什么样,传递一个话哦,这个指针是吧?那现在呢我们呃调用这个linux 的。啊,系统的这个api 呢,我们传入的就是这个文件描述符的,通过这个文件描述符去操作啊,只指向的这个文件啊。好,那。 啊,这节课呢我们就介绍到这啊,我们下一节课呢再见。

# ✔️20-open打开文件(对标fopen

Linux的『系统IO』函数

好了,那么在前面呢我们已经学习了标准c 库io 函数

和linux 系统i o 函数的区别啊

然后呢我们也知道了他们之间的关系什么关系啊,其实就是调用和被调用的关系,对吧?那接着呢又给大家去介绍了一下虚拟地址空间啊,还有文件描述符。

  • 那么掌握了这些内容以后呢,『『我们接下来呢就==正式去学习linux 系统的io函数==』』

  • 那么linux 系统i o 函数啊非常多啊,我们这节课呢先去学习这个他当中的open函数和这个close函数啊。

那么在这儿呢,『『大家要知道啊,就是呃我们这个linux 系统i o 函数啊,其实跟这个标准c 库的i o 函数啊,他们是有这个对应关系的啊。』』 那你比如说呢我们这个标准c 库的啊fopen函数

对吧?那么它其实跟我们linux 系统i o 函数的open 函数是不是就对应起来了? 对吧啊,因为我们都知道这个标准c 库的io 函数啊,它其实底层呢会去调用啊这个linux 系统的io 函数。

  • 好,那么接下来呢我们就去看一看这个open 函数啊,它的一个说明文档。
whoway@ubuntu:~/nowcoder/20.open$ man 2 open

OPEN(2)                           Linux Programmer's Manual                          OPEN(2)

NAME
       open, openat, creat - open and possibly create a file

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这个man指令去查看它的一个手册啊。那么『『这个linux 系统i o 函数或者说==linux 系统的api==。啊,这些函数它呢是在这个第二章啊,』』而

『『这个第三章呢是标准c 库的这些a p i 函数的一个说明。』』

那我们啊比如说呢我们接下来啊要去学习的是这个linux 的啊这些系统的i o 函数。那我们应该到这个第二章去查找。明白吧?如果说你要查看的是这个标准c 库里面的这个函数的一个a p i 啊,或者说它的一个说明文档,那你这个地方呢应该到第三章去查找。

开始写代码

那现在呢我在这儿呢写上注释啊。 写上注释,我把这些内容啊给他拿过来。那

1、首先呢大家看一下,我要使用这个open 函数啊。他要这些头文件,对吧?好,那我把这个头文件啊,我们就放到上啊。那么为什么要这个三个头文件呢?我们只是调用一个函数,对吧?怎么会要有三个这个头文件的? 那这个呢一会给大家去说啊啊

2、然后呢这个两个是。这个open函数啊,它的一个声明就是他的一个原型嘛,对吧?啊,好,然后呢我们把这个放到这啊。那么看到这呢,「大家有没有一个疑问呢?就是说呃在这儿呢我们有两个open 函数对吧?名字相同的两个open 函数,但是这个参数不一样。啊,那么呃学过c 加加的都知道啊,这个在c 加加当中它是什么呀?函数的一个重载是可以的,对吧?但是在这个c 语言当中,它是没有这个函数重载的。那==他怎么能有两个啊同名的这个函数呢?啊,你注意了这个地方呢,它并不是函数的一个重载啊,而是他通过了一个呃可变参数==。就是说他后面呢是一个可变参数,通过可变参数呢来实现了这样的一个效果啊,这点呢大家一定要搞清楚。」

  • 好,那如果说你自己去看这个呃源代码的时候呢,你其实就会发现它里面呢其实。是一个这个可变参数啊。

好,然后呢我们可以啊继续往下看啊,比如说我们按这个呃往下啊下件我们可以往下移,对吧?去查看啊,那么往下呢其实有。呃,很多的这个说明啊,比如说呃description。

这是什么呀?这是描述啊描述这个函数是干嘛用的,对吧?你看他说什么,他说。the open system point. 他说这个open 函数这个系统调用啊,我们都知道这个linux 系统io 啊啊或者说linux 系统api 啊,我们去调用的话,我们都称之为什么呀? 系统调用对吧?那么他干嘛呢?哎,系统调用呢opens 打开啊,打开什么呢file 啊,然后呢呃由这个pass name 指定的这个路径啊,就或者说这个文件名称的一个。 文件把这个你指定的这个文件给打开,对吧?

  • 那你如果说你要具体的去学这个啊函数的话,你应该呢把这些描述呢都具体的去读一读

具体的读一读。好,那接下来我们就啊给大家去介绍一下这两个啊函数啊,我们先学习第一个。

啊,先学习第一个。

那么首先呢给大家说为什么要有三个这个头文件啊,那么其实啊这个函数啊**,他的声明啊是在这个fcntl.h这个头文件里面呢。啊,这个函数的一个声明是在这个里面的,那么其他两个是什么呢?呃,你看我们这里面呢有一个参数叫flags 啊,这个flags呢它其实啊,他是定义的一些宏。==这个flags**呢它是一些标记啊,就是我们后面那个要学到的这个他的一个权限啊。==比如说啊它是可读啊啊它是只读啊,还是只写啊,还是。 啊,可读可写对吧?那么像这些这个标记啊,其实在我们这个linux 系统当中啊,它给他呢定义了一个宏,定义成了宏,不同的宏啊放到了另外两个头文件里面,明白吧,啊,==所以说呢呃为什么他要有三个头文件==,就是这个原因啊。

  • 那你想一想他放到这个定义成宏放到其他的头文件里面有什么好处啊?
  • 那别人是不是也可以去用啊,对吧?只要把这个头文件给它导入进来,是不是就可以去使用了,是不是?

然后呢我们来说第一个这个open 函数啊,那么第一个open 函数呢它其实作用呢是干嘛的?

作用是打开一个打开一个已经存在的文件啊

而下面一个呢,一般呢是用于干嘛的,就是创建一个新的文件啊,新的文件

啊,这个我们依法再说啊,那么==这个flags它的值可以有哪些呢?我们可以去看一下这个说明文档,==我们往下翻。

我把这个拿过来。我们就放到这个笔记里面啊,放到这个地方。

  • 「区分一下」啊,那么这个only read online 呢就是只读,对吧?all right, only 呢只是。只写啊,然后呃read write 呢就是可读可写,对不对?那么这个跟标准c 库的那个fopen不一样,对吧?那个f o p 里面它是不是用的是这个字符串对吧?就是要么是a 啊,要么是这个r 要么是这个w 对吧?通过这个啊某个字符去指定的一个权限啊,或者某个字符探去指定的这个权限。

好,那么然后呢大家还要知道啊,就是说这三个设置是互斥的互斥的啊,什么叫互斥呢?

  • 就是说你要么是,可读,要么是可写,要么是可读可写啊。==你不能说你可读可写,你把这两个都写上不行啊,只能选一个,就是说这三个当中只能选一个==。

好,然后呢,我们再说这个返回值啊,那返回值呢,它其实返回的是一个文件描述符啊,你可以去看一下他的这个文档我们在这呢按下斜线输入这个return value「也就是搜索」 就是返回值啊,那么他就跳到这个return value 这个地方啊。

你看他说什么呢?他说呃。return the new file. this is. 回不去。 啊,新的一个file desperate 就是文件描述符嘛,我们简称fd 对吧?取得每一个单词的首字母啊,fd 啊哦负一。 if an error, a code 就是说如果说产生了错误,那么他会返返回怎么样返回负一啊,追忆了这个是在我们啊linux 系统api 当中的一个呃这个通用的一个特点啊。就是说在我们这个linux 系统当中啊,这些呃函数啊,这些api 啊,一般情况下呢,就是说如果说调用失败了,那么他就会返回一个-1啊,它是一个通用的一个写法,==大部分都是这样的。啊,当然也有这个例外==,明白吧?所以说以后呢你要呃知道啊,就是说啊看到-1就能够想到啊,他其实就是一个调用失败的一个。

# error number和我们的解决方案-perror

这个错误对吧?过程啊,然后呢再来看啊,他说呃in which case 就是在这种情况下啊,error number is set 啊。 后面单子呢是合适的,意思就是说呃,在这种情况下啊,这个error number 呢将被设置成合适的值

  • 那么这个error number 是啥呢?

好,然后呢我们再来说这个error number 啊,我们写到下面。error number. iron 啊,那么number 呢它是属于这个linux 系统库的啊属于linux 系统库的。或者系统函数库。 啊,然后呢,==他是库里边的 一个全局变量==。

啊,全局变量,那么记录的是这个错误号啊,记住的是这个错误号,什么意思呢? 那么==注意了,这个error number 它不属于我们这个函数,它是属于我们整个linux 系统函数库的==。也就是说在我们这个linux 系统函数库里面呢,它有一个全局变量啊,这个全局变量呢就是。记录啊产生了这个错误号,而且是记录的是最近的最近的错误号啊,是最近的口号,就是说呃如果说你这个呃使用了这个linux 的系统啊函数,对吧?那其实就是系统第二嘛。执行的系统就好。如果说这个系统调用发生错误了,那么这个linux 的系统啊它就会把这个全局变量every number 呢附上合适的这个。错误号啊,那么在这个linux 系统当中呢,它其实呢每一个这个每一种错网他都对应了一个这个啊错误号啊。

**比如说我举个例子啊,比如说。啊,你文件这个没有操作权限,比如说这个错误号呢是1,对吧。然后呢,你这个文件不存在错过号呢,是2啊,然后它一直这个列列出来啊,**然后呢。如果说当你产生了某一个错误,他就把这个错误号给设置到这个error number 上,明白吧?

==那么注意了,我们要这个every number 其实啊没什么太大的作用,对吧?==

我们。应该要就是说你产生的错误,我们要把这个错误它的一个具体的一个描述啊给他找出来,对吧?就是说具体的一个错误的原因给他打印出来。才是我们想要的啊

  • ==那我们啊呃其实可以用一个这个函数啊,这个函数呢叫做perror==
  • 那这个函数呢是呃这个==标准c 库==的啊

好,那么给大家说一下这个perror它的作用啊作用呢就是

打印这个error number.对应的这个错误描述。啊,错误描述。那么它里面呢有一个参数啊,这个s 参数。 那么这个s 参数呢,它是呃其实是用户描述用户描述啊,比如说呢我们可以写一个这个hello 啊,那么实际上它最终会输出什么呢?

啊,那么实际最终如果说最终说出的内容是这样的,就是说

1、首先呢是你的这个用户描述。hello. 2、然后冒号后面就是XXX,XXX就是呃实际的错误描述。

啊,好,那么看到这儿呢,可能大家呢啊还不是特别的这个清晰,对吧?我们下面呢去写一个代码去啊给大家演示一下啊。那么首先呢,我们把这几个投文件啊给它呃放到这。

# 演示代码

  • 我们演示,当前目录没有a.txt文件,我们使用O_RDONLY去尝试用open函数,发现返回-1
whoway@ubuntu:~/nowcoder/20.open$ ls
open.c
whoway@ubuntu:~/nowcoder/20.open$ vim open.c 
whoway@ubuntu:~/nowcoder/20.open$ gcc open.c 
whoway@ubuntu:~/nowcoder/20.open$ ls
a.out  open.c
whoway@ubuntu:~/nowcoder/20.open$ ./a.out 
open⭐️⭐️(这个是我们自己写的)⭐️: No such file or directory
whoway@ubuntu:~/nowcoder/20.open$ ls
a.out  open.c

1
2
3
4
5
6
7
8
9
10
11

代码最后别忘了干嘛呢?就是关闭啊关闭。就是我们可以调用这个close,把这个fd 呢给传递进来啊,这个closs 呢我们也可以去看一下。

man 2 close
1

啊,close. 那么他在哪个这个头文件下是不是?u n n i s td 点xr 对吧?==就是unix 的一个。unix i o r 标准的i o== 我们来写一下umi s t d。

啊,you and i s t d 点h 好,然后这样呢我们调用这个close 传递的是一个fd 啊,你看它传递的是一个fd 就是。 你刚才是不是打开的时候生成了一个返回了一个文件描述符,对吧?你关闭的时候呢,把这个文件描述符呢放进去就ok 了啊。

你看他这个解释,他说。lows 啊file this scooter 就是说关闭一个文件描述符啊,然**后呢呃让这个他呢不能在指向任何的文件,**对吧,啊,==并且呢这个文件描述符呢还能被重用==,对不对?

这个是close loss 他的一个作用啊。

好,那么这节课呢就啊给大家介绍了这个open 函数啊,它的第一种使用方法啊,那这样呢它其实是用来。 打开一个文件啊,那我们也可以去试一下。

比如说我们啊在这个目录下面呢去创建一个文件啊,比如说创建一个a.txt

比如说呢我们在这个地方touch。a 点txt。 是不是有了对吧?那有了他还会报这个错误嘛,不会的啊,我们来编一下。

whoway@ubuntu:~/nowcoder/20.open$ ls
a.out  open.c
whoway@ubuntu:~/nowcoder/20.open$ ./a.out 
open: No such file or directory
whoway@ubuntu:~/nowcoder/20.open$ touch a.txt
whoway@ubuntu:~/nowcoder/20.open$ ls
a.out  a.txt  open.c
whoway@ubuntu:~/nowcoder/20.open$ ./a.out 

1
2
3
4
5
6
7
8
9

然后呢我们去提一下。没错啊,

你看没有任何的这个错误信息的输出吧,对吧?

其实它是调用成功了啊,吊装成功以后呢,就直接关闭了啊那

当然了,我们真实操作呢不可能说你打开以后呢,就直接关闭掉,对吧?

我们打开了以后肯定在这个下面呢需要做什么事情。唉,读写操作吧。对吧,读写操作啊,那么读写操作呢我们后面呢再给大家去介绍啊,我们这节课呢先介绍到这,我们下一节课呢再见。

# open.c

/*
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>

    // 打开一个已经存在的文件
    int open(const char *pathname, int flags);
        参数:
            - pathname:要打开的文件路径
            - flags:对文件的操作权限设置还有其他的设置
              O_RDONLY,  O_WRONLY,  O_RDWR  这三个设置是互斥的
        返回值:返回一个新的文件描述符,如果调用失败,返回-1

    errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。

    #include <stdio.h>
    void perror(const char *s);作用:打印errno对应的错误描述
        s参数:用户描述,比如hello,最终输出的内容是  hello:xxx(实际的错误描述)
    

    // 创建一个新的文件
    int open(const char *pathname, int flags, mode_t mode);
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {

    // 打开一个文件
    int fd = open("a.txt", O_RDONLY);

    if(fd == -1) {
        perror("open");
    }
    // 读写操作

    // 关闭
    close(fd);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 21-open创建新文件♐️

那上一节课呢我们学习了open函数的第一种用法啊,就是去打开一个已经存在的文件。

  • 那么其实这个open函数呢它还有另外一种用法,就是那个带三个参数的啊可以用来创建一个新的文件啊。

那接下来呢我们就去看一看啊如何去使用这个open函数啊去创建一个新的文件。那么在这呢我们新建一个文件啊,比如说呢我们叫呃create.c

好,然后呢我们在这儿啊呃首先呢去把这个main函数啊给它写上。

  • pathname现在不叫打开的文件的路径了,现在叫,创建的文件的这个路径对吧?或者说这个文件的名称啊。 好,然后呢这个flag啊,你注意它的这个参数叫什么呀叫flags「注意是:复数」,就是说它这个地方可以是多个的这个呃标记,对吧?

所以呢我们之前呢是不是说过这个flash呢它是对这个文件的操作权限操作权限啊和其他的这个设置,对吧? 好,那这个,其实这个操作权限呢它是必选的啊,所以呢这个操作权限它是必选项,b选项啊就是必须要有的。 啊这是必选的,而且呢他们是干嘛呀互斥的啊这三个之间是互斥的啊。

  • 好,然后呢还有一个是这个可选项啊可选项。

就说这个flag这个参数啊,它的一个完整的列表啊在下面。

那比如说呢o呃pound,这什么呀? 这是往文件里面追加的,对不对? 比如说呢我们呃刚才呢是不是用这个open函数啊去打开一个文件,对不对? 那么如果说我往里面写数据的话呢,他会把之前的给覆盖掉啊,那我不想让它覆盖,我想在后面追加,那我们是不是应该再增加一个flag? 就是刚才那个呃open。 o。 open的,对吧? 这是追加的意思啊。 然后第二个呢是o呃sim卡。 啊就是同步的意思啊。 然后之前我们不关注啊,我们关注的是这个。 o,correct,operator。 那operator它是干嘛呀? 你看到这个单词应该知道它是用来创建的,对不对啊? 可选项operator。 啊那么他呢是这样的,就是说文件不存在啊,那么它会创建新文件,如果存在的话,他是不是不会创建啊? 对不对? 好,这是第二个参数啊。

# mode_t参数介绍5分30

然后第三个参数啊是叫mod啊mod。那么这个第三个参数啊,首先呢我们看它的一个类型啊,叫mod杠t,对吧? 这个类型呢可能大家没有见过,对吧? 没关系啊,一会儿给大家说。 那么这个mod呢必须是一个八进制的数啊八进制的数表示什么呢表示用户对创建出的啊新的文件的操作权限啊其

实就是表示这个创建出的新的啊这个文件的操作权限。

什么操作权限? 我们来看一下这个linux系统当中啊,比如说我们这里面是不是有这些文件啊?

啊还有这个目录,对不对?那每一个文件或者每个目录,它前面是不是都对应了操作权限啊,对吧?

啊其实就是这一部分啊,总共呢是这里面总共是十个这个字符啊这个第一个呢是表示这个文件的类型,后面九个呢是它的权限,对吧? 每三个一组每三个一组啊,前三个是表示当前的这个用户对我这个啊文件的一个权限,然后中间呢是当前用户所在的组对这个文件的权限,对吧? 还有最后一个呢是其他。 组啊,其他组对我这个文件它的一个权限啊。 好,那么我们继续回到这儿啊,我们说这个是表示我们要创建的这个文件的权限,那比如说啊比如说叫我们给一个值零七七五啊零七七五或者说零七七七,对吧? 那么零开头是表示它是什么? 它是八进制的数,对不对? 八进制数啊。 那么在这儿呢大家要注意了,比如说我们给定了这个它的一个权限啊,就是我们最终创建出来的文件的权限啊,比如说是零七七五,那它最终这个得到的文件的或者说创建出来的这个文件的权限就一定是零七七五吗? 啊并不是这样的啊,它里面呢还涉及到一个这个用mask研码的一个这个东西啊,那咱们来看一下啊,我们面。 哦open,我们往下翻啊,其实在这个帮助文档里面呢,它是有说明的。 好就在这儿啊,大家看一下他说怎么的啊。 argument就是这个mod这个参数啊这个参数啊指定什么呢指定这个文件的mod,就是文件的模式,是吧? 然后呢呃将被应用到我们新创建的这个文件身上啊。 那么我们再往下看啊,他说呃在这儿就说他说啊这个最终的啊你看the mod of the critic,file,就是你将被创建的这个新的文件啊。 它的mod呢呃它的值应该什么呢? 是这个mod遇上这个用。 mask举反啊。 好,我们把这个复制过来啊,给大家去看一看。 啊我们就写到这放到这。 好,我写一下,就是最终的权限是这个啊那我们可以啊自己来推导一下,比如说我们给的是零七七五,对吧? 然后呢我们与上这个用mask取反,那它的结果是多少呢? 或者说呢我们在这儿呢呃拿这个零七七七啊来举例,零七七七是不是最高的权限啊? 啊那注意了,很多同学呢可能不知道为什么是零七七七或者七七五,对吧? 你注意看啊,我刚才给大家看了,在这个里边啊,我们重新打开一个复制绘画啊,i、s或者l,每一个文件它是不是有这个权限啊? 对吧? 每个权限呢就是。 呃每一块啊就是三个,要么读、要么写、要么可执行,对不对? 那么它其实是用这个呃八进制的数去表示的啊就是呃如果有这个读的权限啊,还有这个写的权限啊,还有这个可执行的权限,▁x,那么它整个这个结果就是七七七啊七七七就说就是整个这个值啊大家看啊,如果有读、有写、有可执行,对吧? 那么这一组啊如果说你是一个二进制数啊如果你是一个二进制数,都是一吧是吧都是一,那么整个值就是七。 那有三组这个呃权限嘛,对吧? 一组呢是当前用户的这个对文件的权限,第二呢是当前用户的这个所在组对这个文件的权限,第三个呢是其他组对这个文件的权限,是吧? 好,那所以说如果说嗯全部都是有权。 的话是不就是七七七呀? 对吧? 那这个地方用的是什么呀? 用的是这个八进制啊八进制的数,明白吧? 那比如说呢我们呃有一个呃他没有这个读的全或者没有写的权限,对吧? 没有这个写的权限,那它的结果是多少? 是不就是五啊? 对吧? 因为这个它代表四嘛,他如果唯一的话就是r有值的话,它就是代表四。 转换成十进制啊然后▁x呢就代表一嘛,加起来是不是五啊? 对不对啊? 所以说它是这样的一个这个用法啊。 好,那咱们来看一下,那比如说零七七七啊我们比如说我们给的值呢是零七七七啊那么换成这个二进制啊,或者说呢我们先看啊他是不是要跟这个you mask进行一个取法。 那you mask是多少呢对吧u mask是多少呢? 我们看一下,在这个地方啊,我们可以直接啊输入这个u。 max。 诶大家看,you。 mask的值是零。 零二,是吧? 是零。 零零二啊零。 零二。 好,那么其实啊不同的用户啊这个优mask的值啊是不一样的。 比如说我们现在是普通用户脑科的,那么它的值呢是呃零。 零零二,那么如果说你是root用户的话,它其实是零。 零二啊这是默认的,对吧? 当然这个值呢是可以改的,那我们先不改啊,我们就拿这个值来看。 那u mask是零。 零。 二。 二,我们对它取反,取反的话应该是怎么取啊取反的话应该怎么取呢? 其实就是零变成一,一变成零,对吧? 但是现在是这个八进制,我们得转换成这个呃二进制,是吧? 那其实我们也不用转啊,比如说这个呃它最大值是七。 七。 七吧,是不是零。 七七七啊那么取反的话其实就是拿零七七七减这个啊零零零二啊零。 零零二,那么最终得到了什么呀? 是不是零七七五啊? 啊大家可以自己去算一下啊。 所以呢我们这个地方应该是拿零。 这个七七七啊与上以上零七七五八零七七五啊零七七五。 好,那么这个时候雨的话我们就不能直接去推导了啊,我们得转换成什么呀转换成这个二进制了。 那零七七七的话转换成二进制是多少七呢? 其实就是幺幺幺嘛对吧? 一个七是幺幺幺啊然后幺幺幺幺幺幺对吧那么七七五的话就是这样的,幺幺幺幺幺幺。 然后呢这个五的话是不是就是幺零幺对吧? 幺零幺啊就是呃这个是一。 一表示四,然后后面一个一嘛,表示一加起来是五,对吧? 转换成二进制。 好,然后呢接下来就是与了,那这个语其实就是呃大家学过的这个谓语运算,那谓语运算它是什么呀? 未运算。 是不是零和任何数都为零啊? 对吧? 好,那零和任何输入框选项就是安慰语啊,那么零和任何数都为零啊,那这个你怎么去理解它? 你把一看成真嘛,对吧? 那如果是真,争和争是不是为争啊? 对吧只要有假,你是不是整个表达式都有假呀? 对吧? 所以说这个语其实跟我们逻辑语其实也有点这个类似,是吧? 那么最终得到的结果是什么样? 是不是雨的话是不是前面还是这个一,然后这个有一个零还是零吧? 然后最后一位还是一吧,对吧? 得到的结果还是这个得到的结果是零七七五啊。 那你会发现我们传递进来的是零七七七,那么你最终得到的是什么呀? 零七七五是不是少了某些权限啊对吧少了某些权限啊。 所以说呢这个u。 max卡它的作用是什么呢? 我写一下啊。 u mask它的作用啊就是干嘛呢? 呃这个抹去啊抹去某些权限啊抹去某些权限啊,让我们的这个创建的这个文件或者目录啊它的权限呢更加合理一些。 啊所以有的时候啊,你看抹去的是什么? 现在抹去的是呃最后一个最后一个组,就是其他组它里面的某一个权限嘛。 什么权限? 就是第二个写的权限嘛,是不是? 其他组的用户。 那对我这个文件进行写啊,是吧? 那如果你给七七七的话,是不是其他用户都能够对你这个文件进行操作,是吧? 所以呢他呃这个系统呢他为了保证啊你这个权限呢没有给错啊,他会用这个you mask呢去做一些啊抹去权限的一个操作啊所以说它底层最终是用这个mode与上啊这个you mask取反啊,其实这个you mask呢你可以自己去设计啊,比如说这个我们设计成零二二啊零二二。 那么他会把什么呢他会把中间这个中间这三个权限,中间三个权限是呃当前用户所在组的那个权限,对吧? 他把这个里面的这个写的权限就给抹除掉了,大家可以自己去试一试啊,推导一下最终结果肯定是中间这位零为零,就是这一位为零,把这个当前用。 户所在组的那个权限啊对文件的权限,它的写的权限也给抹除掉抹除掉啊。 好,那么这个呢new mask呢大家只要了解一下就ok了啊,我们其实也可以在这个地方呢设置这个you mask啊,比如说u mask零。 二。 二,然后呢我们再输入这个u mask。 啊是不是变成了零。 零二号啊? 对吧? 零零二二。 啊那么这个呢只是在我当前终端有效,如果说我换一个终端,我打开一个重新打开一个会话啊然后universitions他就变了啊,他还是原来的这个值,明白吧? 啊你在这儿设置,它只是对当当前的这个终端有效。 那其实在我们这个程序当中啊,我们也可以用这个u mask去呃对它进行操作啊我们来看一下。 my? are you mask? 你看是不是有一个you mask这样的函数,对吧? 哎系统函数啊我们可以自己去设置啊这个。 值,在程序当中啊去设置这个值啊。 好,那这样呢我们就用默认的就行了。 ok吧? 那么接着我们继续往下看啊,那么现在呢我们通过代码呀去操作一下啊。 那首先呢我们在这儿呢干嘛呀? 去啊创建一个新的文件。 好,我们通过这个open去调用啊。 那首先呢你要把这些头文件啊给它呃包含进来,对吧? 好,然后呢open第一个参数呢是我们要指定的这个文件的啊这个路径啊。 比如说呢我们生成一个这个create点txt,然后呢他的这个flag啊,那flag一个我们要有这个读写权限,比如说我们创建一个新文件,我。 要对它进行读写啊,那我就呃写一个这个o。 red,rather,对吧? 好,然后呢我们是要去创建一个信用文件,我们要加上一个另外的一个标志,就是叫o。 correct,对吧? 这是呃可选项,前面的是必选的,这个是可选的,对吧? 好,然后呢最后一个是这个权限,我们给一个零啊这个七七七虽然我给的是零七七七,但是最终它生成的这个文件它的权限会是零。 七七嘛? 不会啊。 是不是有那个呃用mask这个研码去进行一个操作,对吧? 好,那么接下来呢他会返回一个什么呀? fd啊,就是文件描述符。 好,然后呢我们判断一下,如果这个f t等于负一,那么我们就p。 air打印这个错误嘛,对吧? 这个p,l它是在这。 这套文件里面啊,u、n、i、s,t,d点h啊就是unix的一个标准的一个头文件啊,标准库啊。 然后呢把什么呀把这个呃你要描述的信息啊,oppo放进去啊,它是不是就可以输出了? 好,然后呢最后呢我们还要干嘛? 关闭,对吧? 关闭这个文件描述符close啊,就是你关你操作完了以后一定要关闭。 好,那么这个close其实我们看这个p、l啊,它不是在这个呃在这个u、n、r、s、t、d,这个点h里面啊,close这个函数才是在这个里面。 那这个p、l呢它是在呃这个标准c库里面的,s、d、d、i、o,点h里面啊,你看现在是不是可以点进来了,对吧? 他在这个标准题库里面的啊。 好,那么。 接下来呢我们就去对这个程序啊进行一个编译和运行啊,是不是有个call? at点c啊? g,cc crazy,case点c,港欧crazy,birthday,回车就生成了一个可执行文件,然后我们去执行一下啊执行。 然后我们来看一下是不是生成了一个crazy,eat点tx啊,我们l l croy at点txt,大家来看一下啊,你看我们指定的权限什么样? 我们指定的权限是零七七七啊,那么他的权限是什么呀? 七。 七和什么呀? 七七五吧。 七七五是不是和我们刚才推导的是一样的,对吧? 他抹去了啊这个中间的一个这个可读的权限啊,就是可写的权限,对吧? 那么你像这个文件是不是他也不需要有这个呃可执行的一个权限啊,是吧? 我们其实。 这也可以把这个呃▁x这个权限啊给它抹除掉啊那么你就要自己去设置这个或者说设计这个u mask它的值了啊,让他能够把这个▁x权限呢给他抹掉啊。 好,那这个呢我们呃不去重点关注啊,我们只要知道它这个里面呢会有这样的一个机制就ok了。 然后呢再给大家去介绍一下这个地方啊,我们是不是呃设置这个标签啊? 是吧? 我们用的是这个按位货,对吧? 安慰祸。 那么为什么要用安慰祸呢? 给大家解释一下,大家来看一下这个open函数啊,我们面啊oppo,大家看这个open函数的啊,这个第二参数是int类型吧是int类型,对不对? 那么int类型它占几个字节啊? 我们我们在这个地方呢写上注释啊。 啊就是flag,aflix参数是一个int类型的数据,那么它是占四个字节,那么占四个字节呢它总共呢是三十二位,对吧? 三十二个啊这个比特位三十二个这个二进制位啊。 那么大家想一想啊,就是说我们啊呃给他加了这个标,就是o。 re的rather,设置了这个值,然后呢又设置了这个o。 correct。 为什么要用这个安慰惑呢? 那么其实啊在这个程序里面呢,它其实就是这个flag啊flags,呃三十二个这个位,对吧? 它有三四个字节嘛,三十二位嘛,那每一位呢其实这是一个标志位每一位就是一个标志位。 啊代表一种情况啊,举个例子啊,比如说我们画一个这样的这个数组啊,其实不是数组,它就是一个三十二位的这个数据嘛,对吧? 等等等等啊。 那我们啊从这个比如说这个是第零位啊,我我们还是画正确的吧。 啊那么这是最高位,比如说这是第一位的数据,然后第二位然后这是第三十二位,对吧? 那么其实这个程序它是怎么做的呢? 比如说啊呃每一个位它是不是都是只有零和一这个值啊是不是零和一这个值啊? 对吧? 那么注意了,每一位它就代表了一个这样的flag这样的一个标记啊标志。 我举个例子,比如说第一个比如说第一位,它就代表的是这个呃可读这个。 即第二位就代表这个可写这个标记啊第三位呢就代表这个可读可写这个标记,第四位呢就代表这个operator,就是创建创建的这个标记。 啊那么你看,如果说我有读的权限,那么他这个这一位的标记的值啊这一位上的值就是一,如果可写,那么当然这个是互质的,对不对啊? 那么如果说有这个创建的这个啊这个标记,那么它第四位就是第四个标比特位啊就是为一,如果为零的话,他就是没有这个标记,明白吧? 啊没有这个标题啊。 所以说为什么大家想一想,为什么这个地方用货呢? 为什么用货呢? 其实用货的话,比如说我们一开始啊有一个rd,red,rd,root是什么呀? 是第三个标记位啊,这是举个例子,第三个标记位是一。 其余的目前是不是都是零啊? 其余的都是零嘛,对不对? 然后呢我们又增加了一个o,correct,o crited,它其实是第几个标记位啊? 是第四个标记,第四个标记位变成一了。 那么我们用这个安慰惑的话,我们说安慰惑有一则为一,对不对? 那么其实就是把这个标记位加到这个呃三十二位的这个数据里面,是不是让这一位唯一啊? 对吧? 所以说为什么要用货啊为什么要用货就是这个道理啊。 好,那呃这是我们第一次接触这样的一个东西啊,啊大家呢一定要好好的理解啊。 那我们使用多了以后呢,自然而然的就能够明白到底什么含义啊。 那再举个例子,比如说啊比如说呢我我现在呢有一个这个文件的权限啊,对吧? 呃那比如说一开始都是零啊三位嘛,对吧? 但我要增加一个这个写的权限哎我是不是跟谁谁去比较一下? 跟这个呃一、一、零、零啊,就是读的权限啊加一个读的权限,跟跟他呢进行一个安慰祸是不是就加到这个里面去了? 对吧? 安慰祸以后这个是不是就变成一了? 是不是最终就变成了一、零、零,对吧? 那如果说你要加一个呃写的权限,是不是要跟零、一、零进行一个按维护? 就是又把这个一又加到这个里面,对吧? 可执行的话,那你就要跟零、零、幺进行一个安慰,或就把这个呃可执行这个权限加进去了,是吧? 其实就是每一个标记位啊,每一个这个标记啊它代表的是我们这个呃三十二位的这个数里面的某一个标记啊标志位比如说比如说我们这个呃可读可读的这个权限,是吧? 那么它就是代表第一。 的标记为啊第一个标记为一,那么是说明它有这个标记啊,它其实就是代表一个值嘛,对不对啊所以说这个地方呢大家一定要搞清楚啊,为什么我们要用的是这个安慰货啊。 好,那这个呢就是我们使用open函数啊去创建一个新的文件啊那这节课呢我们就先介绍到这啊,我们下一节课呢再见。

# create.c

/*
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>

    int open(const char *pathname, int flags, mode_t mode);
        参数:
            - pathname:要创建的文件的路径
            - flags:对文件的操作权限和其他的设置
                - 必选项:O_RDONLY,  O_WRONLY, O_RDWR  这三个之间是互斥的
                - 可选项:O_CREAT 文件不存在,创建新文件
            - mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775
            最终的权限是:mode & ~umask
            0777   ->   111111111
        &   0775   ->   111111101
        ----------------------------
                        111111101
        按位与:0和任何数都为0
        umask的作用就是抹去某些权限。

        flags参数是一个int类型的数据,占4个字节,32位。
        flags 32个位,每一位就是一个标志位。

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

int main() {

    // 创建一个新的文件
    int fd = open("create.txt", O_RDWR | O_CREAT, 0777);

    if(fd == -1) {
        perror("open");
    }

    // 关闭
    close(fd);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 22.readwrite函数h

# copyfile.c

/*  
    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
        参数:
            - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
            - buf:需要读取数据存放的地方,数组的地址(传出参数)
            - count:指定的数组的大小
        返回值:
            - 成功:
                >0: 返回实际的读取到的字节数
                =0:文件已经读取完了
            - 失败:-1 ,并且设置errno

    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
        参数:
            - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
            - buf:要往磁盘写入的数据,数据
            - count:要写的数据的实际的大小
        返回值:
            成功:实际写入的字节数
            失败:返回-1,并设置errno
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {

    // 1.通过open打开english.txt文件
    int srcfd = open("english.txt", O_RDONLY);
    if(srcfd == -1) {
        perror("open");
        return -1;
    }

    // 2.创建一个新的文件(拷贝文件)
    int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
    if(destfd == -1) {
        perror("open");
        return -1;
    }

    // 3.频繁的读写操作
    char buf[1024] = {0};
    int len = 0;
    while((len = read(srcfd, buf, sizeof(buf))) > 0) {
        write(destfd, buf, len);
    }

    // 4.关闭文件
    close(destfd);
    close(srcfd);


    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# ✔️23.lseek函数(对标fseek

你比如说这个呃linux 当中的lseek函数啊,它在这个啊标准c 库里面其实是这个fseek

好了,那上一节课呢,我们学习了read 和range 函数啊。 啊,read 函数呢它是用来从文件当中啊去读取数据。好,这个run 函数呢是网文件当中的去写数据啊,那我们用这个read 和right 呢。欲写了一个简单的程序啊,就是去实现这个文件复制的功能。

  • 那这节课呢我们来学习一下lc 的函数啊。

那首先呢我们去创建一个目录。 啊,lesson11。

然后我们去查看一下啊l c k 这个函数,它的一个说明文档啊。

如果说同学们学过这个呃标准c 库当中的一个这个函数啊,其实这个lseek 个函数呢你也能够学会这个

我们之前啊在介绍这个呃标准c 库io 和这个linux 系统i o 函数的时候呢,我们说过他们其实是有这个对应关系的,对吧?

  • 你比如说这个呃linux 当中的lseek函数啊,它在这个啊标准c 库里面其实是这个fseek

对吧,那如果说你这个fseek函数 啊你学会如何去使用的话,其实这个l seek 你也知道怎么去使用啊,我们把这个标准c 库的这个。函数啊也给他复制一下啊,给大家看一下,我们对照着来看。

好,那你对照着这个s d k 和l c k 来看。 **其实他们两个的差别在哪?其实就是在于第一个参数,**对吧?第一个参数呢呃这个标准c 库里面呢,这个ftk 他用的是file。 那么第二个参数啊outset 都是一样的。然后第三个参数是vs 啊也是一样的。

好,那在这儿呢我再给大家去介绍一下啊这几个参数的一个含义。 啊,首先说参数,第一个呢是这个fd。啊,fd 呢它其实是我们通过open 啊得到的,对吧?他其实是文件描述符。 啊,通过这个open 得到,那么我们可以通过啊通过这个fd 去操作某个文件。 然后第二个参数呢是outset。好outset,那offset 呢在这个标准c 库里面啊,它的类型是long,对吧?但是在这个呃linux 系统函数里面呢,它是o f f 杠t 啊,那其实这个o f f 杠t 啊我们也可以去看一下啊。呃现在呢他还没有啊,还没有我们一会儿写代码的时候去看一下他其实。 其实跟这个呃long 是一样的啊,类似的。

# 目前

好,那outset 我们也写一下,他呢是这个偏移量。 啊,偏移量。然后第三个呢是这个once。啊,one s 那它是什么呢?它是啊我们需要指定一些这个啊标记啊,我们通过这个帮助文档去看一下。好,咱们来看一下,就是看下面啊,那大致的意思呢,其实就是我们通过这个lseek函数啊啊去对什么呢?对你指定的这个文件描述符,它所对应的那个文件。进行一个啊文件指针的一个偏移啊,我们都知道这个文件指针对不对,它是可以用来去操作我们的这个文件的啊

那么这个lc 函数呢,它其实就是对这个文件指针进行一个操作啊,那这个one s 它有哪些这个值呢?我们来看一下啊。 比如说有thick set 啊。seek court, 还有seek end。啊,

  • 那你一看这几个词啊,你就知道他们表示什么含义,是吧?

  • 好,我们复制一下,其实这几个词跟这个呃就是我们标准c 库里面。

他的这个几个值啊是一样的啊,我们可以去看一下。

  • 比如说这个six set 他是干嘛的呢?它是设置文件指针的偏移量啊,就是表示设置,对吧?那偏移多少呢?就是通过这个offset 去指定的。

  • 然后seek current 啊,那它呢是也是设置偏移量。

啊,也是热水屏障,只不过呢它是从当前位置啊,你看这个seek 后面跟的是什么current,对吧?啊,表示从当前位置。然后加上什么呀?加上第二个参数offset 啊的值。 啊,就是说从你当前的这个位置往后偏移,这个offset 啊这个偏移量啊,那这个sign 呢它也是设置偏移量。 设置偏移量啊。好,那么这个呃,设置的偏移量呢,它是什么呢?它是就是文件的大小加上这个第二个参数offset。 他的值。 啊,那么这个地方啊为什么是文件的大小来,因为你看它的这个是什么?是end,就是文件结尾,其实就是文件结尾加上第二个参数offset 的值。啊,天移到这个位置啊。而这个six seek set 呢,它是啊就是直接设置啊,从这个文件开始啊。的一个偏移量啊,这是这三个参数啊。

那么呃其实这个lc 可函数啊,它的作用呢其实就有几个啊给大家呢去写一下。

那第一个作用。啊,第一个作用就是移动文件指针啊,到这个头文件。啊,那么为什么我们要移动这个指针到头文件了,或者说我们为什么要去移动这个文件指针呢?比如说我有一个文件对吧?它这里面呢都是这些。二进制的数据对吧?那你读的时候你可能读到中间了啊,那你说有没有一种可能啊,就是在我们这个程序当中啊呃有这样的一个需求啊,我想反复的去读,我想再回到。这个开始啊重新去读,肯定是有这样的一个需求的,对吧?啊,那这个时候呢我们就可以通过这个lseek啊去设置我们这个文件指针的偏移量,然后呢进去进行啊第二次的操作,对吧?

啊,那如果说你呃不通过这个lseek的话,你是不是你得干嘛呀,你得关闭掉这个文件,重新打开啊,再重新去操作吧,是吧?那就比较麻烦啊。好,那所以说第一个作用呢就是移动文件指针啊到这个头文件,那这个怎么写呢? 好,这个地方呢应该是到文件头啊。 这是第一个作用。

第二个作用呢其实是用来获取啊当前文件指针的位置。

那第个作用呢就是。可以去获取文件长度。

第四个这个作用啊。第四个作用呢它可以干嘛呢?

好,那这前面几个啊我就不给大家演示的非常简单,我给大家呢去演示一下第四个功能啊就是去。 拓展文件的长度。 那么在这样呢,我们去写一个main函数。

好,那首先呢我们啊呃在这里面呢去新建一个文件。 比如说呢新建一个hello 老茧txt 可以吧,那这里面呢目前呢是没有任何数据啊,或者呢我们呃不写的话呢也没有关系啊。 啊,我我们写一下嘛。hello 点t s t 啊,比如说呢我们呃写一些数据,比如说hello。哇呀。啊,保存一下啊。好,我们通过这个地方啊。看一下这个hello 点txt 他一共是11个字节是吧?那现在呢我们写一个程序啊,去拓展这个哈罗点tc 它的一个长度。

ls -l看到hello.txt的大小
1

好好,然后再来重新编译一下。 好,再去运行一下。好了。那我们看到啊它运行成功了是吧?i sl.

# 坑的地方,扩展文件需要我们写入一点东西才做事。

我们看一下这个hello 点txt 原先是11个字节啊,有没有变成就是我们往后拓展100,他有没有变成111样,并没有对吧?(==竟然还是11==)那么你要注意了这个。==呃,我们如果说要想扩展文件的话呢,我们移动了这个指针啊,我们最好呢在后面呢还要干嘛呢?来写入啊一个这个啊空数据(坑点)==。

那这个lc 卡我们刚才实现了这个文件拓展的一个功能,是吧?

那很多同学呢会有疑问了,就是这个拓展的功能它有什么作用呢?

啊,举一个例子啊,比如说呢大家都用过这个==下载软件==,对吧?那你比如说呢你下载一个这个文件啊,它的这个大小呢,比如说有5G 啊,举个例子,比如说有武器对吧?那你的磁盘呢,你是不是下载的时候呢,你肯定也有可能在用这个磁盘的,对不对?那下载我们都知道下载5G 的话呢,可能会耗费一些时间,可能呢要半个小时,对不对?

那如果说这个时候呢,你你的磁盘呢,它剩余的这个大小呢。他没有5G了。那你说你这个下载5G 的文件还能正确的下载下来吗?是不能了,对吧?那么这个时候呢我们其实可以怎么做,我们在下载的时候啊,其实一个下载软件啊它。提前呢先把你把这个五g 的啊这个文件的一个大小给它占用了。哎,那这样了以后呢,他是不是在慢慢的往这个里面写数据啊,再再把这里面的这个零。啊,替换成这个具体的一个数据,它下载下来的数据对吧**?啊,那这个呢就是lsc 它的一个用途啊,他可以去拓展这个文件啊,我们在下载的时候呢。先把你这个文件呢给它创建出来啊,这个大小呢给你拓展出来**。然后呢再慢慢的往里面去下载啊。那么这个时候如果说你这个你去使用磁盘的话,对我们下载这个文件。

啊,有没有影响啊,没有吧,对吧?因为5G 的这个呃文件的大小都已经占用了啊,已经占用占用好了啊,不可能出现问题。

# lseek.c

/*  
    标准C库的函数
    #include <stdio.h>
    int fseek(FILE *stream, long offset, int whence);

    Linux系统函数
    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
        参数:
            - fd:文件描述符,通过open得到的,通过这个fd操作某个文件
            - offset:偏移量
            - whence:
                SEEK_SET
                    设置文件指针的偏移量
                SEEK_CUR
                    设置偏移量:当前位置 + 第二个参数offset的值
                SEEK_END
                    设置偏移量:文件大小 + 第二个参数offset的值
        返回值:返回文件指针的位置


    作用:
        1.移动文件指针到文件头
        lseek(fd, 0, SEEK_SET);

        2.获取当前文件指针的位置,,,,因为lseek的返回值,是off_t返回的其实是最终,所在的位置!
        lseek(fd, 0, SEEK_CUR);

        3.获取文件长度
        lseek(fd, 0, SEEK_END);

        4.拓展文件的长度,当前文件10b, 110b, 增加了100个字节
        lseek(fd, 100, SEEK_END)
        注意:需要写一次数据

*/

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

int main() {

    int fd = open("hello.txt", O_RDWR);

    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 扩展文件的长度
    int ret = lseek(fd, 100, SEEK_END);
    if(ret == -1) {
        perror("lseek");
        return -1;
    }

    // 写入一个空数据
    write(fd, " ", 1);

    // 关闭文件
    close(fd);

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# hello.txt

  • 见文件
hello
1

# ✔️24.statlstat函数(模拟shell的stat)

这节课呢我们去学习一下s t a t 和l s t a t 这两个函数啊。那首先呢打开这个h r。 我们在这呢去创建一个目录,lesson 12啊,进入到lesson 医药目录下。

那首先呢我们去看一下这个说明文档啊,我们看一看这个s t a t 函数啊,它是干什么用的。往往下翻。 啊,还有一个啊description 就是描述对吧?

他说this function s 就是这些函数啊啊返回information 啊,返回这个信息。关于什么的呢?about a file. 啊,

关于一个文件的对吧?

也就是说啊这个函数啊,我刚才说的这两个函数啊,它是返回一个文件的相关的一些信息。

啊,那么继续往下看,他说。听the buffer pointer to bite。啊,statbuff 啊,那么这个是啊。 我们调用这个函数啊,需要传入的一个参数啊,那么他会把这些信息呢放到这个参数里面,就是放到这个参数里面传递进来的参数里面。

啊,就跟我们之前啊使用这个,,啊read 啊这个函数,我们是不是传递进去一个数组,对吧?他会把读取到的数据啊放到这个我们传递进去的这个数组里面。 啊,这就是这个stat函数它的一个作用啊,我们在这儿呢去写一下。啊,首先说它的作用。 作用呢就是啊获取一个文件相关的啊一些信息啊。那么至于有哪些信息呢,其实啊我们啊。 在这个h 二当中啊啊在这个linux 当中啊也有一个命令叫stat 啊,通过它呢可以去查看一个文件的啊它的一个信息。那你比如说呢我们去啊创建一个文件啊,touch, 比如说a 点txt。

XXXX@XXXX:~$ stat a.out 
  File: a.out
  Size: 13808     	Blocks: 32         IO Block: 4096   regular file
Device: fc10h/64528d	Inode: 56230796    Links: 1
Access: (0775/-rwxrwxr-x)  Uid: ( 1011/   huwei)   Gid: ( 1011/   huwei)
Access: 2022-08-12 16:10:23.217208788 +0800
Modify: 2022-08-12 16:10:21.865188066 +0800
Change: 2022-08-12 16:10:21.865188066 +0800
 Birth: -
1
2
3
4
5
6
7
8
9

image-20220814084516103

是不是有一个a 点tsd 啊,是吧?我们可以用s t a t 指令后面的跟上这个文件的名称回车啊,那能够看到这个文件啊,这个a 点t i s t。 这个文件相关的一些信息啊,我们把这个a 点t f t 它里面的数据啊给它啊增加一些啊,**比如说我们增加一个hello word。**好,保存并退出。 啊啊然后呢我们再重新用s t a t,然后呢a 点txt 去查看一下。

好,那大家来看一下有哪些信息啊。比如说第一个文件。 是文件名对吧?a 点txt 然后大小呢是十二个字节啊,呃快呢是总共是八个块啊,然后呃i o 块呢是。 四零九六啊,那这个其实是它的一个一个快所占用的一个大小啊,总共呢占用了这个八个块。然后呢,后面是普通文件啊。那如果说你是英文的话呢,他照样呢是用英文去表示的啊。好,然后呢设备啊设备就是我这个文件所在的这个设备的一个。这个标识啊,然后还要按照这i node 呢,它其实是在这个linux 当中啊啊用来指定啊或者说用来标识我们一个文件的一个这个值。i know 的。就是我们这个a 点tfd 这个文件啊,它的id no 的值啊,然后呢后面是硬连接啊,这是硬连接数,它有一个硬连接,对吧? 这是一个普通的文件嘛。好,然后下面是权限啊,大家看一下。

啊,谁呀,老科的对吧?g i d 呢就是组id 啊也是一千,然后脑科等啊,然后下面呢是最近访问的时间。 啊,你看

这是最近访问的时间,对吧?

还有最近更改的时间啊,然后还有这个最近改动的时间啊,那这个更改和改动有什么区别呢?

啊,这个更改呢其实是呃主要是指的他的这个文件里面的内容数据啊,包括他的这个文件的属性发生了这个改变。 啊,他这个时间呢就会变更,然后这个改动主要是指它的这个属性啊,比如说我这个文件啊,它的权限化能改变了,对吧?那么他改动呢其实是这个时间。当然你这个权限改动了,它上面呢也会改啊,这两个呃有细微的这个差别。然后下面还有这个创建时间,对吧?他没有列出来啊。

# 用途

其实我们用这个stat函数啊,它呃获取到的信息呢,其实就是啊我们在照显示的啊这些信息啊,当然了他不止这么多信息啊。还会有一些其他的额外的信息啊。

好,那我们接着来看一下这个s t t 函数啊,我们来说一下它的这个参数。

那这个s t a t 上说和l s t t 这两个函数它的参数是一样的。

那么第一个参数是叫pass name 啊,这个很明确。他是什么呀?

他是我们要操作的文件的这个路径。 对吧你要获取哪个文件的这个信息啊,那你把这个路径给我传递进来,对吧?

第二呢是statbuff. 好,那么它传递进来的是一个s t a t 类型的这个指针,它是一个结构体啊,那么这个结构体啊我们写一下它是一个结构体变量啊,而且呢我们这个地方呢它的参数啊是一个传出参数啊。 传输参数啊,为什么呢?我们说你把这个结构体变量传递进来啊,那么这个函数它获取到这个文件相关的信息以后,是不是会对我们这个传递进来的参数进行操作,把这个信息写到这个里面去,对吧?它是一个传出参数啊,就跟这个啊r 的函数,我们传递一个数组进去啊,那个参数是一样的,对吧?那么这个是用于保存啊获取到的这个文件的信息。啊,这是这两个参数,然后返回职场的返回值,我们来看一下啊。

# 目前

对吧?好,那这是s t a t 它的一个这个参数,还有返回值啊,

那同样的这个l s t a t 它的参数和返回值的这个说明啊也是一样的。**那么至于他的这个作用啊,跟这个s t a t 它的区别,**我们一款呢给大家去说啊。好,那么现在呢我们还还要研究的一个东西啊,就是这个传递进来的这个参数啊,s t a t 类型的这个结构体啊,它tt bug 对吧?那么其实这个结构体啊,他在帮助文档当中啊,他也有介绍。

# stat结构体

我们往下翻。啊,你看在这它是不是有一个s t a t 结构体啊,那这里面呢有他的一些成员啊,成员的类型,成员的名称啊,还有这个成员是表示什么含义?

还有这个st_rdev。那这个呢如果是一个设备文件的话,那这样呢记录的是他的这个设备编号啊,设备文件的设备编号。

好好,那现在呢大家应该能够清楚啊,这个结构体它到底是干嘛用的,对吧?

它是用来保存啊获取到的这些文件的信息,就分装到这个结构体变量身上啊。

那你要把这个结构体变量传递给我这个函数啊作为参数传递进来。那么他会给我们自动的去复制。 然后呢再来给大家说一说这里面比较关键的一个东西,就是这个呃s td 就是st_mode 啊,它是mode_t类型的啊啊。 那么这个s t_mode 它其实呢就是一个16位的整数啊,我们具体的来看一下它是如何去记录啊,这些权限,还有这个文件类型的。

# st_mode变量

那么我们。照样呢这个st_mode 呢,它一共是十六位,对吧?那他怎么去记录这个权限和文件的类型的,它是用这个标志位,每一个标志位去记录的。啊,那这个标志位我们之前是不是也给大家介绍过,对吧?啊,之前我们也遇到过类似的啊,用某一个这个标志啊,就是我们之前啊比如说创建这个呃要通过这个open 打开文件。 还有这个创建文件对吧?它是不是有这个药传递一个flag,那那个flag 它里面的内容是不是就是通过这个标记位去进行一个操作的,是吧? 那同理这个s t t mode 它也是啊类似的,他也是这样的啊,那我们来看一下,比如说它的前3位呢啊就是前三位。 这是零一二,这三位呢它是表示others 就是其它用户啊,它的一个对我这个文件的权限啊。那如果说我有这个可执行权限。

啊,这个大家应该能看懂,对吧? 然后呢,他照样呢也有对应的这个红纸啊,我们来看一下,

比如说s_IROTH。 好,i r 呢就是呃表示is read就是是否有读的权限,对吧?

拿走ots 就是other thus 的意思,对吧?他是这个英文单词的缩写啊。

那你看它是不是用两个这个标志位啊。去表示一种类型啊,是吧?所以说呢这个文件类型啊它跟后面这个权限不太一样,它是通过这个呃一个或者葛标记位去表示的。 啊,那么在这呢,比如说我们呃想要去判断一个文件啊是否有这个呃执行的权限,或者说这个有这个写的权限啊,比如说我要判断。

大家能明白这个意思吧啊,所以说这两这两种啊,一个是文件类型,一个是这个权限,他们的这个判断的操作是不一样的啊

。那么如果说你想去获取他的权限的话,是不是就是我刚才说的这个方法去获取啊? 比如说你要获取这个当前用户啊,他是否有读的权限,那你就去跟这个某一个宏定义,对吧?比如说这个啊。 r user 个这个做一个按位与的操作,如果是0的话,说明他没有这个权限。如果1的话,说明他有这个权限,对吧?

就把这个标志位给大家获取到了。 如果是文件类型的话,我们怎么去判断是不是先跟这个掩码进行一个安文宇把那几个位给他获取出来,然后再跟这个宏指进行一个判断。对吧就可以啊判断是否是哪一种文件啊。那这个呢我们就呃这节课呢我们先不去做啊,

好,然后呢我们再来说一说这个。

# lstat 函数

啊,这个l s t t 函数是干嘛用的呢?大家来看一下啊,我们是不是有一个a.txt文件啊,那我是不是可以给他去创建一个软链接,对吧?

ln -s a.txt b.txt
1

啊,然后是不是创建出了一个软连接b.txt

image-20220814093655128

啊,你看它是一个软链接啊l 那么它是一个这个软件接力类型的,它是指向的这个a 点tnt 是吧?

那我现在呢我通过s t a t 一点t i s t 去看一下他的信息啊,大家看一下它的信息是怎么样,是不是是这个呀。 是吧是不是这个好,那它的大小5五,其实我们来看一下啊,其实这个b 点tsc 它的大小事务吧。 对吧事务啊啊,然后a 点txt 是十二,然后呢我们v i m 去看一下b 点txt 它其实打开的是什么?打开的是a 点txt。

对不对?那么你注意了,如果说我们用这个s t a t 函数去获取这个软链接啊这个文件的信息的话,它其实获取的是什么呢?获取的是这个啊指向的那个文件的啊a.txt它的信息啊

image-20220814093802323

那如果说我就想获取这个b 点txt 这个软链接的信息,其实是这段对不对?

那我就要用到什么呢?就要用到这个lstat这个函数啊,这个就是这两个函数之间的一个区别,

  • 一个是用来获取这个啊具体的那个文件的信息
  • 一个是用来获取这个软链接文件的。啊,而不是获取我这个软链接指向的那个文件的信息。

# stat.c

/*
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>

    int stat(const char *pathname, struct stat *statbuf);
        作用:获取一个文件相关的一些信息
        参数:
            - pathname:操作的文件的路径
            - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
        返回值:
            成功:返回0
            失败:返回-1 设置errno

    int lstat(const char *pathname, struct stat *statbuf);
        参数:
            - pathname:操作的文件的路径
            - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
        返回值:
            成功:返回0
            失败:返回-1 设置errno

*/

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

int main() {

    struct stat statbuf;

    int ret = stat("a.txt", &statbuf);

    if(ret == -1) {
        perror("stat");
        return -1;
    }

    printf("size: %ld\n", statbuf.st_size);


    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# ✔️25.模拟实现ls -l命令(stat函数的应用)

那stat函数呢它是用来获取一个文件的相关的信息,啊而这个lstat函数呢它是用来获取一个软链接文件的信息。

啊那这节课呢我们用这个std函数啊去做一个小的案例啊。

那什么案例呢? 那我们啊都用过这个ls -l这个指令,对吧?

那我们这节课呢我们要去写一个程序啊啊比如说呢我们写一个呃这个程序生成了一个app这样的可执行程序,对吧? 然后呢我们在这个可执行程序后面呢带上一个文件的名称啊然后我按回车,那它能够把这个文件相关的信息啊就跟这个l s杠l一样啊跟这个指令一样,把这个文件的信息呢显示在这个呃终端当中啊我们要做这样的一个程序。

./app a.txt
1

好,那我们现在呢就去写一下这个代码啊。

那么我们啊打开这个with code,我们就直接新建在这个listen12目录下啊,我们新建一个文件啊,那这个文件呢我们起一个名字啊,重新新建一下啊,比如说叫ls-l.c

# 目前

给对比着这个结果来看,对吧? 好,这是我们通过这个l s杠l啊这个指令,后面呢跟上这个文件的名称啊,把这个文件的信息呢列出来。 那下面呢我们要去写一个这个程序啊,啊模拟这样的一个这个功能。 那我们怎么来做呢? 那首先呢我们呃将来呢你是不是要写一个这个可进程序,对吧? 运行它,然后后面要跟上这个文件的名称啊,对吧? 那你是不是要通过这个呃传递参数给这个慢函数啊? 把这个文件的名称给它传递进来,对吧?

# main函数的参数-意义

那我们在这儿呢补充上这个main函数的参数啊,一个是a、r、g、c啊,一个呢是这个恰新argv,啊这是参数,它是一个呃字符串、数组。

好,那么接着呢我。我们怎么做呢?我们先判断一下啊,如果说你的这个a、r、g、c的就是参数的个数啊<2啊那么你注意了,我们调用这个慢函数啊,它会传递多少个参数给他呢?

啊至少会有一个啊

  • 第一个是我们这个文件的啊,就是你输入这个可进程序啊它的一个名称,对吧?或者路径啊。

然后其余的参数呢是你在这个命令行里面啊,在这个后面的参数它会它会放到这个呃数组里面去,对吧? 好,那也就是说我们这个程序呢就是一般情况下会有两个参数啊,一个呢是呃我们这个文件文件的这个名称,一个呢是你这个可经程序它的一个名称或者路径啊。

XXX@XXXX:~$ ./a.out demo.c 
-rw-rw-r-- 1 XX XX 2440 Sun Aug 14 10:02:51 2022 demo.c
XXXX@XXX:~$ ./a.out 
./a.out filename

1
2
3
4
5

# 目前

接下来我们去获取

# 修改的时间

那么修改的时间呢我们是通过st_mtime啊m,time去获取的啊那么这个地方啊我们来看一下它是什么类型啊。 它是它是它是我们我们继续点进去,它是一个再点啊我们再点进去。 啊你看它是一个什么类型啊? 是long int

  • 那也就是说这个时间啊它其实获取的是一个秒数,从哪到哪的描述呢?是从这个一九八零年啊一月一号零时零分零秒到目前的啊到就是到我这个修改时间的那个时间所有的一个描述啊。

那我们最终是不是要转换成这个具体的时间啊?

那么在这儿呢我们要用到一个这个呃函数,叫==ctime==啊,这个函数呢它是在这个time.h头文件里面的啊。 那么这个c time函数它是用来将这个秒数啊转换成这个本地的时间啊,c time,我们直接调用一下啊,把这个参数呢传递进来。

# ls-l.c


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>

// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 nowcoder nowcoder 12 12月  3 15:48 a.txt
int main(int argc, char * argv[]) {

    // 判断输入的参数是否正确
    if(argc < 2) {
        printf("%s filename\n", argv[0]);
        return -1;
    }

    // 通过stat函数获取用户传入的文件的信息
    struct stat st;
    int ret = stat(argv[1], &st);
    if(ret == -1) {
        perror("stat");
        return -1;
    }

    // 获取文件类型和文件权限
    char perms[11] = {0};   // 用于保存文件类型和文件权限的字符串

    switch(st.st_mode & S_IFMT) {
        case S_IFLNK:
            perms[0] = 'l';
            break;
        case S_IFDIR:
            perms[0] = 'd';
            break;
        case S_IFREG:
            perms[0] = '-';
            break; 
        case S_IFBLK:
            perms[0] = 'b';
            break; 
        case S_IFCHR:
            perms[0] = 'c';
            break; 
        case S_IFSOCK:
            perms[0] = 's';
            break;
        case S_IFIFO:
            perms[0] = 'p';
            break;
        default:
            perms[0] = '?';
            break;
    }

    // 判断文件的访问权限

    // 文件所有者
    perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
    perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
    perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';

    // 文件所在组
    perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
    perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
    perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';

    // 其他人
    perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
    perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
    perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';

    // 硬连接数
    int linkNum = st.st_nlink;

    // 文件所有者的名称⭐️⭐️⭐️getpwuid	(pw  uid)在pwd.h中,你man一下
    char * fileUser = getpwuid(st.st_uid)->pw_name;

    // 文件所在组⭐️⭐️⭐️getgrgid	(group gid)
    char * fileGrp = getgrgid(st.st_gid)->gr_name;

    // 文件大小
    long int fileSize = st.st_size;

    // 获取修改的时间⭐️⭐️⭐️
    char * time = ctime(&st.st_mtime);

    char mtime[512] = {0};
    strncpy(mtime, time, strlen(time) - 1);

    char buf[1024];
    sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);

    printf("%s\n", buf);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

# ✔️26.文件属性操作函数✔️4个

# ppt

int access (const char *pathname,int mode) ;

int chmod (const char *filename,int mode) ;

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

int truncate(const char *path,off_t length);
1
2
3
4
5
6
7

好了,这节课呢我们去学习一下跟文件属性操作相关的一些函数啊。 主要呢有下面四个,

  • 第一个呢是access,那它呢是用来判断文件的权限,或者呢是去判断文件是否存在
  • 第二呢是change,mode啊,chmod,那它呢是用来修改文件权限的。
  • 然后第三个呢是change,owner啊,chown它是用来修改文件的所有者啊或者呢是所在组
  • 然后第四个呢是truncate,那它呢是用来缩减或者扩展某一个文件的一个大小啊。

# access函数

那下面呢我们一个一个来看,首先呢我们去学习一下这个access这个函数啊。

好,然后呢我们去看一下这个access这个函数啊。

呃他说这个access啊检查啊检查当前的这个进程,就是我调用这个函数它的一个进程是否能够访问啊这个文件,对吧?

好,那么然后呢它其实下面呢也有介绍啊就是说呃就是说我们还可以去判断这个文件呢是否存在啊。

/*
    #include <unistd.h>
    int access(const char *pathname, int mode);
        作用:判断某个文件是否有某个权限,或者判断文件是否存在参数;
        - pathname:判断的文件路径
        - mode:
            R_OK:判断是否有读权限
            W_OK:判断是否有写权限
            X_OK:判断是否有执行权限
            F_OK:判断文件是否存在
        - 返回值:功了返回0啊那么-1说明他调用失败了
*/
#include <unistd.h>
#include <stdio.h>

int main(){
    int ret=access("a.txt", F_OK );
    if( -1==ret )
    {
        perror("-1 -1 -1\n");
    }
    else
    {
        printf("文件存在");
    }
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# chmod函数

/*
    #include <sys/stat.h>
    int chmod(const char *pathname,mode_t mode);
        修改文件的权限
        参数:
            - pathname:需要修改的文件的路径
            - mode:需要 修改的权限值,八进制的数
        返回值:成功返回0,失败返回-1
*/
#include <sys/stat.h>
#include <stdio.h>
int main(){
    
    int ret=chmod("a.txt",0775);
    if( -1==ret )
    {
        perror("失败");
        return -1;
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

好,那它的类型呢是model杠t啊,这个类型咱们之前是不是见过啊?对吧啊它其实是一个这个啊它里面是通过这个标记位啊。去操作我们这个权限的啊。 那我们通过这个卖文档啊去看一下啊这个model杠t啊,就是这个参数啊,它需要指定什么啊? 呃其实你可以指定这些红值,能看懂吗? 可以指定这些红值,这个宏的值呢非常多啊,你比如说有这个s啊,e字这个什么呀?

哎这我们往下看,is▁,ra,id,u、s、r,就是这个我当前的用户,u、s、r是否有这个读的权限,对吧? 啊你看后面也有解释。

red by air就是我所有者是否有读的权限。 那当然了,你这个mod呢也可以去给它指定一个啊八进制的数啊,这个需要修改的这个权限值啊。

# chown函数

好,那接着呢是change,owner。,这是修改呃文件的它的一个所在组或者呃所有者啊那这个呢非常简单啊,

  • 呃就不给大家演示了啊,

我们就是把这个它的一个这个帮助文档啊来看一下卖号,change,owner。 啊就是他那他的这个头文件呢是在这个unix st地点h头文件里面,那么大家注意看,第一个参数呢是文件的名称,对吧? 第二个呢是什么u,id杠t,online,这个是所有者的那个它的一个id啊,就是所有者,你要修改成哪个所有者啊就是哪个用户,对吧? 它的一个用户id后面呢是所在组啊,那你要改成这个啊,所在组的那个就是组的id。 明白吧? 啊那么这个所有者的id所在组的id怎么看呢?

vim /etc/passwd
1

啊在这儿呢我们可以去看一下这个etc,一个呢是password里面啊,在这个文件里面呢,它把这个所有的用户啊都写在这个里面了啊你看这个脑壳的这个用户,我们当前这个登录的用户,他的这个你看这是用户名吧,对吧? 然后这是它的一个什么呀? 诶所所有者它的一个这是id啊这是用户的id,然后后面冒号,后面是这个所在组的这个啊组的id啊。 好,这是查看一个文件啊,就是查看一个用户他的一个这个id怎么去查看?

那么其实我们如果说要查看这个组的话,我们也可以去通过查看etc里面有一个group这个文件啊这里。

vim /etc/group
1

但是我们当前系统当中啊,所有的这个啊组啊,比如说有root组,它的id呢是零。

啊那你可以自己去创建一个新的用户啊你比如说呢我们去通过这个user and啊去创建一个幸运的用户,比如说创建一个gaogao用户啊那么这样呢他说是权限有问题,我们用速度哈提拳,然后呢去创建一个用户,啊然后呢输入这个密码。 好好了,然后呢我们id去查看这个高高用户,你看他的这个用户的id是不是1001啊? 对吧?组的id呢是。 也是幺零零幺啊组呢是幺零零幺这个组,然后呢组名呢是高高啊你默认创建一个用户呢,他的这个呃就是他的这个祖名啊跟这个用户名呢是一样的,对吧?

啊好,那么你就可以去修改,修改的话你就把这个呃用户的它的一个id,还有这个组的id是不是写到这个函数里面啊看看是不是写到这个参数里面,是不就ok了啊?

就可以修改啊。==那么如果你修改的话呢呃如果说你没有这个呃权限的话,其实你是修改不了的啊==。

  • 那如果说你要呃有权限的话呢,你在执行这个可进程序的时候,你前面加上这个sudo啊,那加上以后呢你就会发现啊程序执行了以后呢就能够修改某一个文件它的一个所有者或者所在组啊。

# truncate函数

然后呢我们再来说一下最后一个,那它呢是用来去。啊对我们这个文件啊进行一个缩减或者是啊扩展的啊,那这个用的也是比较多的

man 2 truncate不能直接man 不然就是1号
1
hello world
1
  • 上面的b.txt是12个字节
/*
    #include <unistd.h>
    #include <sys/types.h>
    int truncate(const char *path, off_t length);
        作用:缩减或者扩展文件的尺寸至指定的大小
        参数:
            - path:需要修改的文件的路径
            - length:需要最终文件变成的大小
*/
#include <unistd.h>
#include <sys/types.h>
#include<stdio.h>
int main() {
    
    int ret=truncate("b.txt",20);
    if( -1==ret )
    {
        
        perror("失败");
        return -1;
    }
    else
    {
        printf("成功");
    }
    
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

那么先说一下这个它的作用重要呢是用来去缩减啊或者呢是扩展啊文件的这个尺寸

其实就是文件的这个大小啊啊尺寸至这个指定的大小。

  • 测试改成20,后边补充
  • 测试改成5,就只剩下hello了

所以说呢这个truncat呢它可以用来干嘛呀

它可以用来做这个我们文件的一个缩减,或者呢是啊拓展我们这个文件的一个尺寸,啊指定就是拓展到我们指定的一个大小,或者说缩减到我们这个指定的一个大小。啊那如果说你是缩减的话呢,它会把这个多余的这个数据呢给它啊删除掉对吧给它移除掉。

  • 那如果说你是这个拓展的话呢,它会啊把这个拓展的这个内容都是使用这个零啊其实就是这个啊空字符去替换啊。

# ✔️27.目录操作函数

int mkdir (const char *pathname,mode_t mode) ;
int rmdir (const char *pathname) ;
int rename (const char *oldpath,const char *newpath) ;
int chdir (const char *path) ;
char *getcwd (char *buf,size_t size) ;
1
2
3
4
5

好了,这节课呢我们去学习一下跟目录操作相关的一些函数啊。 那首先第一个呢是mkdir啊,那我们看到这个名字啊你能够想到什么呀?是不是想到我们这个shell命令对吧? 那在这个项命令里面是不是有一个mkdir这样的命令? 它是用来去创建一个目录的啊。

那同样的这个mix二函数啊其实跟这个mkdir这个==指令呢是对应的啊==。

那第二呢是呃rmdir,他呢是去删除一个空文件啊。

然后呢最后一个呢是get cwd。

  • 啊那么这个命令呢==它其实跟我们啊这个shell命令啊有一个叫pwd==,对吧?啊它的这个功能呢是一样的,它是获取当前的啊这个路径。

那下面呢我们分别呢去给大家呢去看一下啊这几个函数如何去使用。

# mkdir.c

/*
    #include <sys/stat.h>
    #include <sys/types.h>
    int mkdir(const char *pathname, mode_t mode);
        作用:创建一个目录
        参数:
            pathname: 创建的目录的路径
            mode: 权限,八进制的数
        返回值:
            成功返回0, 失败返回-1
*/

#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    int ret = mkdir("aaa", 0777);

    if(ret == -1) {
        perror("mkdir");
        return -1;
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# rmdir不演示了

那这个作用呢非常小啊,它只能删除这个空目录啊。

  • 啊如果说有内容的这个目录呢,你要先把这个目录里面的内容给它删掉,然后再去啊调用这个函数,它才能够啊执行成功啊,才能够删掉啊那这个就不演示了。

# rename.c

  • rename函数是,
  • 好,那你看这个是什么呀? 这个是标准的这个c库里面的,是吧? c库里面的这个内容啊。 好,那我们就直接去。
/*
    #include <stdio.h>
    int rename(const char *oldpath, const char *newpath);

*/
#include <stdio.h>

int main() {

    int ret = rename("aaa", "bbb");

    if(ret == -1) {
        perror("rename");
        return -1;
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# getcwd和chdir.c函数

# ⭐️修改工作目录的函数chdir⭐️

/*

    #include <unistd.h>
    int chdir(const char *path);
        作用:修改进程的⭐️⭐️⭐️工作目录⭐️⭐️⭐️
            比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder
        参数:
            path : 需要修改的工作目录

    #include <unistd.h>
    char *getcwd(char *buf, size_t size);
        作用:获取当前工作目录
        参数:
            - buf : 存储的路径,指向的是一个数组(传出参数)
            - size: 数组的大小
        返回值:
            返回的指向的一块内存,这个数据就是第一个参数

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

int main() {

    // 获取当前的工作目录
    char buf[128];
    getcwd(buf, sizeof(buf));
    printf("当前的工作目录是:%s\n", buf);

    // 修改工作目录
    int ret = chdir("/home/nowcoder/Linux/lesson13");
    if(ret == -1) {
        perror("chdir");
        return -1;
    } 

    // 创建一个新的文件
    int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    close(fd);

    // 获取当前的工作目录
    char buf1[128];
    getcwd(buf1, sizeof(buf1));
    printf("当前的工作目录是:%s\n", buf1);
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# ✔️28-目录遍历函数

DIR *opendir (const char *name) ;	打开1个目录
struct dirent *readdir (DIR *dirp);	读取目录,那读取目录读取什么呀读取目录里面的内容,对吧?
其实我们这个目录呢它也是一个文件啊。
int closedir(DIR *dirp) ;	第三个呢是close的就是去关闭一个目录啊,那他跟我们之前啊学过的一个函数close非常像,对吧?
1
2
3
4

我们通过这个open函数啊可以去打开一个已经存在的文件,那当我们使用完毕以后呢,我们需要调用close函数把这个文件给它关闭。批了,对吧?那同样的这个目录啊,当我们打开以后啊使用完毕以后呢,我们也需要使用这个close、ir这个函数啊把这个目录啊把这个资源呢给它关闭掉啊。那么我们有了这三个函数以后呢,我们可以去完成一个功能啊,什么功能呢

  • 就是去读取某个目录下啊所有的这个普通文件的一个个数

# 注意-这3个函数第man 3里面-因为是标准C

那么我们啊通过这个卖文档啊去查看一下这三个函数啊,

那么这三个函数呢它是在这个第三第三章当中的啊。 好,然后第三章当中呢是这个标准c库的一些函数,对吧?

啊代表你这个呃这个你传递进来的参数的这个目录啊然后呢它会返回一个policy,返回一个指针,指向谁呢? 指向一个dir stream(目录流) 我们说这个目录它其实也是一个文件,对吧?那它既然是文件,它里面是不是也有一些数据啊,对不对也是这些数据啊。

那么我们可以把它看成是这个目录流,

我记得,我好想有
aaa/
vim aaa/ 会有奇怪的显示
1
2
3

也就是说他说这个流啊一开始的时候呢是呃在哪儿在这个我们第一个目录的实体啊就是第一个这个实体的啊这个第一个目录的实体就是在最前面嘛,对吧?在这个流的最前面。

好,然后呢我们在这儿呢一个一个来说啊,第一个呢是干嘛呀?打开一个目录啊打开一个目录,然后呢它的参数啊参数呢是不是只有一个name,对吧? 只有一个name,那么这个name它是什么呀? 需要打开的啊目录的名称,对不对? 那么再来看它的返回值啊,那返回值呢它返回的是一个呃d ir新的一个类型,那么你可以把它理解为什么呢理解为就是呃理解为这个目录流啊目录留信息,它其实是一个结构体,我们可以去看一看啊,我们写一个麦函数。 啊然后呢我们把我们把这个头文件给它导入进来啊包含进来。 好,然后呢我们去呃输入这个d,r,然后按control啊进来。 那么它是一个什么呀结构体,对吧?

它的这个具体的结构体其实是这个啊,但是这个呢它是系统它私有的,我们看不到,对吧? 你看他说什么? 他说this is that,time。 啊这是个是数据类型,o、d、e、r、a、s,p,s,pen,of jack,就是呃目录流的一个这个类型啊然后呢他说实际的这个结构体呢他说呃对我们这个用户呢是不开放的,对吧啊好,那我们看不到啊,我们不用管它,我们只需要用到它就ok了,对吧? 我们用一个变量接收它,然后后面呢再去使用它就ok了。

# 目前

然后你再调用一次是不是读的是第二个,对吧? 再调一次读的是第三个,它会往后移的啊。 好,那这个是读取目录,那么它的这个参数啊参数是这个d、n、r啊d、i、r、p,或者说参数啊,d、i、r。 p是什么呢? 是这个我们通过这个open down啊返回的结果,是吧? 那么然后呢再来看这个返回值啊,返回值呢它是一个结构体啊结构体,那么他干嘛呢? 它其实就是代表读取到的文件的信息啊,你每调一次reveal,他会读取这个信息,他会把这个信息呢分装到这个结构体身上啊。 好,那我们可以去看一下这个结构体啊,其实在这个呃里面呢也有啊,慢三,red得往下看,他是不是定义了一个这个d、i、r、e、n、t这样的结构体啊? 那么它里面呢有这么一些成员啊,你可以去看一看啊,这样的都是英文的啊,然后在这个ppt里面呢我给它翻译成中文。

# dirent结构体

那什么是d name呢?d、n、m是在这个最下面,它其实是文件名啊。呃他这个是一个数组啊,呃大小呢是二百五十六,然后呢这个这个呢是我们这个文件名啊实际的一个这个长度,对吧?比如说我定义了一个这个数组啊去保存这个文件名啊,它的这个大小呢是二百五十六,但是最终呢它不一定能装满二百五十六,对吧?可能这个文件名呢只有十个长度。

那么这个d、i、c论呢就是表示那个文件的这个实际的长度啊然后呢这个deta版就是我们当前获取的这个文件啊,读取到了这个文件它的一个文件的类型啊。

那么文件类型呢?

它一共有这么几种啊,在我们这个linux当中啊它是7种,是吧? 还有一种呢是这个未知啊就是说呃除了这七种以外啊都是未知的这个文件

那么d、t、i盘它可以取这些宏纸啊,你可以跟这些红纸呢去进行一个比较,比如说快设备就是dt。 啊,data,time,block啊,然后字幕设备呢就是dt、c、hr,对吧? 那我们一会儿呢要判断这个普通文件应该跟谁判断? 是不是d、t、i、g,对吧? regular啊普通文件就是规则文件、普通文件啊。

# 目前

那么你只要记住啊,如果读取到了这个末尾啊,读取到末尾什么意思啊? 就是说没有文件可以读了呗,对吧? 比如说我们有这样的一个目录啊,我读到最后了啊,那下面再往下就没有可以读了了,那么它会返回NULL是吧?

l,你看在这个目录下面是不是有点还有点点这两个目录啊,对不对? 那这两个目录我们需要对它进行一个操作吗? 不需要吧? 对吧? 点表示当前目录,点点表示什么? 上一级目录,是吧? 那难道你还要去呃判断这个点点,然后再进入到上一级目录再去操作吗? 不用吧。 所以说我们要干嘛呢? 先获取这个名称。 要判断判断是点。 点还是点点,如果是这两个的话,我们要忽略掉,对吧?

# readFileNum.c

/*
    // 打开一个目录
    #include <sys/types.h>
    #include <dirent.h>
    DIR *opendir(const char *name);
        参数:
            - name: 需要打开的目录的名称
        返回值:
            DIR * 类型,理解为目录流
            错误返回NULL


    // 读取目录中的数据
    #include <dirent.h>
    struct dirent *readdir(DIR *dirp);
        - 参数:dirp是opendir返回的结果
        - 返回值:
            struct dirent,代表读取到的文件的信息
            读取到了末尾或者失败了,返回NULL

    // 关闭目录
    #include <sys/types.h>
    #include <dirent.h>
    int closedir(DIR *dirp);

*/
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getFileNum(const char * path);

// 读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]) {

    if(argc < 2) {
        printf("%s path\n", argv[0]);
        return -1;
    }

    int num = getFileNum(argv[1]);

    printf("普通文件的个数为:%d\n", num);

    return 0;
}

// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {

    // 1.打开目录
    DIR * dir = opendir(path);

    if(dir == NULL) {
        perror("opendir");
        exit(0);
    }

    struct dirent *ptr;

    // 记录普通文件的个数
    int total = 0;

    while((ptr = readdir(dir)) != NULL) {

        // 获取名称
        char * dname = ptr->d_name;

        // 忽略掉. 和..
        if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) {
            continue;
        }

        // 判断是否是普通文件还是目录
        if(ptr->d_type == DT_DIR) {
            // 目录,需要继续读取这个目录
            char newpath[256];
            sprintf(newpath, "%s/%s", path, dname);
            total += getFileNum(newpath);	//递归的写法----
        }

        if(ptr->d_type == DT_REG) {
            // 普通文件
            total++;
        }


    }

    // 关闭目录
    closedir(dir);

    return total;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
  • 注意,你如果用上面程序去统计/目录

可能曝出

opendir: PeInission denied
1

权限拒绝

# ✔️29.dupdup2函数

  • 读作(大per函数)
int dup (int oldfd) ;复制文件描述符

int dup2 (int oldfd, int newfd);重定向文件描述符
1
2
3

《TCP/IP网络编程》书上是用这个『在同一个进程中“复制”(其实是用另一个文件符来模仿)这个文件符,然后好实现,“半关闭”啥的

这节课呢我们去学习两个跟『文件描述符操作相关的函数。一个呢是dup(读作大pe)函数,一个呢是dup『读作大per』函数啊,那么这个

  • 大部函数呢它是用来复制文件描述符的
  • 然后这个dup2函数呢。它是用来重定向文件描述符的啊

mkdir lesson16

man 2 dup
1

啊,其实就是旧的这个文件描述符,对吧?他的一个数值你传递进来啊,那么他会拷贝一个什么呢?考过一个新的这个文件描述符。啊,那么他会干嘛呢?using the lowest 就是最小的对吧?最小的这个number 的啊unused 的是吧,没有被使用的这个文件描述符啊,就是为我们这个呃新的这个拷贝的这个文件描述符,对吧啊?

那么这个很好理解啊,我们之前啊在介绍文件描述符的时候啊,我们来看一下啊。我们之前在介绍文件描述符的时候,我们说呃一个这个进程当中啊,他有一个文件描述符表,对吧?啊,一共呢是1024个。那

么前三个呢是已经被占用了零一二,对吧?对应的是我们这个标准输出标准输入和标准错误啊,默认是打开的。那么我们去使用的时候,它默认是。从这个最低的啊,那肯定是三呗,对不对?那么如果是十三的话,我去对这个比如说这个三啊,这个它对应的是fd 啊,它的这个变量是m f d。 那么我对这个up fd 呢进行一个大的操作,进行一个拷贝。

那么它的作用呢是干嘛呢,就是复制一个新的文件描述符啊,那么新的文件描述符它跟这个你这个传递进来的参数啊。就是旧的这个文件描述符啊,他们是不是指向同一个文件的啊,肯定是的,对吧?他们是可以,他们是指向同一个文件的。我们之前。也说过啊,多个文件描述符可以指向同一个文件,对不对啊

# dup.c

/*
    #include <unistd.h>
    int dup(int oldfd);
        作用:复制一个新的文件描述符
        fd=3, int fd1 = dup(fd),
        fd指向的是a.txt, fd1也是指向a.txt
        从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符


*/

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

int main() {

    int fd = open("a.txt", O_RDWR | O_CREAT, 0664);

    int fd1 = dup(fd);

    if(fd1 == -1) {
        perror("dup");
        return -1;
    }

    printf("fd : %d , fd1 : %d\n", fd, fd1);

    close(fd);	//我们关闭fd,但是由于fd1也指向a.txt所以,我们后边还可以借助fd1去修改

    char * str = "hello,world";
    int ret = write(fd1, str, strlen(str));
    if(ret == -1) {
        perror("write");
        return -1;
    }

    close(fd1);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 目前

那么你看我们这个dup函数是不是也是一样的呀? 哎,就是你返回一个新的这个啊文件描述符fd 一跟我们旧的这个文件描述符,它指向的是同一个文件。 对吧,所以说他的这个说所说的复制其实是指的啊指向的是同一个文件,对不对?

# dup2.c

/*
    #include <unistd.h>
    int dup2(int oldfd, int newfd);
        作用:重定向文件描述符
        oldfd 指向 a.txt, newfd 指向 b.txt
        调用函数成功后:newfd 和 b.txt 做close, newfd 指向了 a.txt
        oldfd 必须是一个有效的文件描述符
        oldfd和newfd值相同,相当于什么都没有做
*/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

    int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
    if(fd1 == -1) {
        perror("open");
        return -1;
    }

    printf("fd : %d, fd1 : %d\n", fd, fd1);

    int fd2 = dup2(fd, fd1);	//注意,返回的fd2应该和fd1(也就是新的fd一样)
    if(fd2 == -1) {
        perror("dup2");
        return -1;
    }

    // 通过fd1去写数据,实际操作的是1.txt,而不是2.txt
    char * str = "hello, dup2";
    int len = write(fd1, str, strlen(str));

    if(len == -1) {
        perror("write");
        return -1;
    }

    printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2);

    close(fd);
    close(fd1);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# ✔️30.fcntl函数

  • 读作,f,control函数
int fcntl (int fd, int cmd, ... /* arg */ );
    复制文件描述符
    设置/获取文件的状态标志
1
2
3

《TCP/IP网络编程》书中对于这个是用,那个『好像是“紧急传输数据”,但是TCP不支持233』

f control 函数呢

它不仅仅只有1个功能,那在这个整个linux 当中啊,这个f 很翘函数啊。 他一共可以做5件事情啊,那我们啊只需要掌握其中的2件事情就ok 了。

一个呢是它可以用来去复制文件描述符。

啊,另外一个功能呢,它可以设置或者呢是获取我们这个稳健的一个状态标志。

啊,那下面呢我们就去具体的看一下。 那首先呢我们去查看一下这个a control 它的一个man文档。

man 2 fcntl
1

# 目前

fcntl的文档:

第1个传递一个这个文件描述符对吧?那么第二呢是啊英特类型的这个参数,common 的啊common 的。那这个地方呢应该是传递的。啊,是一个这个命令啊,好,或者呢是传递的啊,当然这个命令呢是这个action 跳函数啊,它定义了一些宏啊。然后后面这个点点点表什么意思啊?哎点点点表示哎它是一个可变的一个参数,对吧?也就是说呃你这个地方呢。

那呃我们刚才说啊这个a control 呢在整个linux 当中啊,它可以做五件事情,对吧?

那我们需要掌握的是两个

  • 一个呢是啊,可以去复制文件描述符啊,

第二个呢是他可以去干嘛呢?去啊,对我们这个啊文件描述符的一个状态进行设置或者获取。

# ⭐️阻塞是对函数的描述!

那么这样呢就提到了一个这个名词啊啊一个呢是阻塞,一个是非阻塞啊,这样呢也给大家去啊扩充一下啊阻塞。 和非阻塞。

啊,那什么是阻塞什么是非阻塞的?其实呃阻塞和非主任他描述的是这个函数的行为啊,函数调用的行为。 啊,函数调用的行为好,那么呃阻塞函数呢,它啊阻塞函数它是在调用结果返回之前呢啊比如说我调用一个这个函数啊,比如说叫啊爱的。 啊,比如说我调了一个这个爱的函数啊,那么

我在这个呃调用的过程当中呢,我在我这个返回值没有返回之前啊,我当前的进程啊或者说线程啊被挂起了并且呢是在得到这个结果之后才会返回啊,那么这种我们称之为什么呢?称之为==阻塞==。

啊,那么非阻塞呢就是啊我调用这个函数。那么这个函数呢它立马呢就能够干嘛呢?

啊,他立马就能够返回。好,那呃我返回的话,有可能啊得到了这个想要的结果,有可能呢没有得到想要的这个结果啊,==但是呢它不会阻塞当前的这个进程或者线程。==

啊,那么这种是什么呀?这种就是啊这个非阻塞啊,非阻塞。

那么你比如说啊我们现在这个终端啊,我去复制一个终端,大家看一下啊。 好,那我复制一个终端,大家说这个终端它是阻塞的还是非阻塞量,它其实是一个阻塞的。啊,为了**我们终端它也是一个进程啊,**也是一个应用程序,也是一个进程。那么他是目前呢是阻塞,**阻塞等待什么呢?等待我们去输入一些这个指令,**对吧?比如说我们输入一个这个ls

啊,那么他是不是就能够执行了,对吧?

啊,执行完了以后其实就不阻塞。

他在**执行的过程当中,它是不阻塞的,**对吧?

但是呢现在如果说我没有输入任何的内容。他就是阻塞在找啊,等待我们去输入这个就是阻塞,明白吧?

好,那这个阻塞和非阻塞呢,我们啊后面呢还会啊这个在后面讲到这个。

啊,进程的时候呢啊多进程的时候呢,我们还会详细的去说啊啊那这样呢大家呢先了解一下啊,就是阻塞和非阻塞呢它其实描述的是我们函数的一个行为。

啊,好,那这个是对我们这个f control 这个函数啊啊它的一个啊这个说明啊。

那么下面呢我们就去写一个这个啊代码。去演示一下,fcntl函数啊。好,那么在这样呢我们首先呢呃写一个卖函数。

好,那下面呢我们去啊写一个这个代码。那么我们说这个control 呢我们需要掌握的有两个功能,

  • 一个是复制文件描述符对吧?那这个复制文件描述符非常简单,我就简单的给大家去写一下了啊。

# 目前

  • 第二个功能就是修改啊或者获取啊。文件状态的这个flag 啊,好。

那么我们举个例子啊,比如说呢我现在呢去。打开一个文件啊,比如说oppo。啊,打开什么呢?一点t s t。 对吧?

好,然后呢我们呃权限呢,比如说给一个什么呢o。啊,read only. 也就是说我只能去读取这个文件是吧?好,我们在这个下面啊去新建一个一点t s t。啊,里面呢写一些数据,比如说hello 可以吧。 好,那么我打开了以后呢,接下来啊呃我发现啊我需要对这个文件呢哎需要对他进行一个写入的一个操作。目前我是不能写的,那我们可以干嘛呢?去修改这个文件描述符它的一个状态的一个标记啊,就是这个flag。 那么给这个flag 干嘛呢?给flag。加入这个oa 判的。 啊,这个标记这样的话是不是就能够去干嘛呀,去追加数据了,对不对?

# fcntl.c

/*

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ...);
    参数:
        fd : 表示需要操作的文件描述符
        cmd: 表示对文件描述符进行如何操作
            - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
                int ret = fcntl(fd, F_DUPFD);

            - F_GETFL : 获取指定的文件描述符文件状态flag
              获取的flag和我们通过open函数传递的flag是一个东西。

            - F_SETFL : 设置文件描述符文件状态flag
              必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
              可选性:O_APPEND, O)NONBLOCK
                O_APPEND 表示追加数据
                NONBLOK 设置成非阻塞
        
        阻塞和非阻塞:描述的是函数调用的行为。⭐️⭐️⭐️
        阻塞函数呢,他是在返回之前
*/

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {

    // 1.复制文件描述符
    // int fd = open("1.txt", O_RDONLY);
    // int ret = fcntl(fd, F_DUPFD);

    // 2.修改或者获取文件状态flag
    int fd = open("1.txt", O_RDWR);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 获取文件描述符状态flag
    int flag = fcntl(fd, F_GETFL);
    if(flag == -1) {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   // flag = flag | O_APPEND

    // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd, F_SETFL, flag);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }

    char * str = "nihao";
    write(fd, str, strlen(str));
    close(fd);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# 参考资料

  • 牛客,高境