小奥的学习笔记

  • Home
  • Learning & Working
    • Speech Enhancement Notes
    • Programming language
    • Computer & DL
    • MOOC
  • Life
    • Life Time
    • Thinking & Comprehension
    • Volunteer
    • Plan
    • Travel
  • Footprints
  • GuestBook
  • About
    • About Me
    • 个人履历
    • 隐私策略
  1. 首页
  2. Study-notes
  3. Computer & DL
  4. Linux
  5. 正文

Linux基础学习第四天:C库函数和系统函数

2019年4月30日 1175点热度 0人点赞 0条评论

由于前几天都是使用印象笔记写的,尚未转换成Markdown格式,所以会在后续更新。

C库函数


FILE其实本质上是一个结构体,它里面有:

  • 文件描述符(整型值)。索引到对应的磁盘文件。
  • 文件读写位置指针:读写文件过程中指针的实际位置。
  • I/O缓冲区(内存地址):通过寻址找到对应的内存块。大小默认是8个byte。设置I/O缓冲区是为了减少对内存的读写访问,节省时间。

Linux的系统函数是没有缓冲区的,需要我们自己提供。

文件描述符

Linux每一个运行的程序(进程),操作系统都会为其分配一个0~4G(232)的地址空间(虚拟地址空间)。
文件描述符就是在内核区,如上图所示。第0、1、2个是标准输入、标准输出和标准错误,默认是打开状态,也可以被关闭。没打开一个新闻界,则占用一个文件描述,而且使用的是空闲的最小的一个文件描述符。例如我们打开3个文件分别用了3、4和5,这个时候我们关闭了第一个文件,第3个空闲了,再打开第4个文件,那么就占用的是第3个。

用户区

Linxu下可执行文件格式是ELF。

查看文件类型可以使用file app就可以了。如图所示就是ELF。ELF主要包含的三个段.bss、.data、.text,其他还有一些只读数据段和符号段等。
然后下面就是受保护的地址,地址是0~4K。
程序在读的时候首先从代码段开始,全局变量就放在.bss或者.data,局部变量是在栈空间,栈空间是向下增长。用户使用new或者malloc分配的内存是从堆空间分配,堆空间是向上增长。

C库函数与系统函数的关系

上图中展示了C语言中printf函数是如何实现的。如图所示,printf函数相当于是一个stdout,它需要通过FILE* 来进行操作。我们知道一个FILE本质上是一个结构体,里面有文件描述符(FD)、文件读写位置指针、I/O缓冲区(C库函数维护的)。当我们使用printf的时候,它调用了Linux系统的API:

  • 首先调用了应用层的write函数,printf将文件描述符传递给应用层的write,然后将字符串和字符串的长度传递给了write。write只能在0~3G的用户空间操作,它会帮我们做一个空间转换,将用户空间转换到内核空间。

  • 然后从应用层又调用了系统调用层里面的sys_wirte()。这个sys_write()是可以对系统内核进行操作,也就是说对3G~4G范围内进行操作

  • 到内核层以后,会调用显示器的驱动,然后让驱动把字符串显示出来。

以上是整个的工作流程。

Linux的API又分为三层,由上层到底层分别是:
- 应用层,包括write,open之类,操作的是用户空间。
- 系统调用。
- 内核层。操作设备驱动函数等。

open函数

open是用来打开和创建一个文件或设备。语法如下:

#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);

在上述使用方式里面,pathname顾名思义是路径名,flags是打开方式,有O_RDMONLY,O_WRONLY或者O_RDWR(分别是只读、只写和读写),当然还有其它的一些打开方式。

第一个语句用于打开已经存在的文件,而第二个语句用于创建一个不存在的文件。第二个语句flags至少应该是一个可写的,并且需要提供一个O_CREAT,创建不存在的文件需要提供mode参数,也就是访问权限。

参数mode具体指明了使用权限,它通常也会被umask修改,所以一般新建文件的权限为(mode&~umask)。注意模式只被应用于将来对这个文件的使用中。open调用创建一个新的只读文件,但仍将返回一个可读写文件描述符。

open()通常用于将路径名转换为一个文件描述符,当open()调用成功的时候,它会返回一个新的文件描述符(永远取未用描述符中的最小值)。如果出现错误则返回-1,并在errno设置错误信息。

这个调用创建一个新的打开文件,即分配一个新的独一无二的文件描述符,不会与运行中的任何其他程序共享(但可以通过for(2)系统调用实现共享)。这个心的文件描述符在其后对打开文件操作的函数中调用。文件的读写指针被置于文件头。

errno是一个全局变量,当函数调用失败后,errno就会被赋值,值就是对应的错误信息。

open函数中的errno

errno定义在头文件errno.h中(地址为/usr/include/errno.h),它是一个全局变量,任何标准C库函数都能对其进行修改(Linux系统函数更可以)。

其错误宏定义位置:

  • 第1~34个错误定义:/usr/include/asm-generic/errno-base.h
  • 第35~133个错误定义:/usr/include/asm-generic/errno.h

我们以errno-base.h截图来看一下,内容如下所示:

我们又改如何查看错误信息呢?我们可以用perror函数来实现。

项目 说明
头文件 stdio.h
函数定义 void perror(const char *s)
函数说明 用来将上一个函数发生错误的原因输出到标准设备(stderr);参数s所指的字符串会首先被打印出,后面再加上错误原因字符串;此处错误原因依照全局变量errno的值来决定要输出的字符串。

我们来用实际操作实践一下以下三点:

  • 打开一个不存在的文件
  • 打开一个已经存在的文件
  • 创建一个新文件

下面的第一张图显示的是源代码,第二张图显示的是操作和操作结果。

可以看出以下问题:

  • 为什么我设置了新建文件的权限为777,而实际生成的文件的权限是775?如前面我们在介绍open()中的mode中所说,其实本地还有一个掩码umask,这个umask值为0002(如第二张图所示),所以我们实际的权限的计算公式为(mode&~umask),umask取反后做按位与操作。

接下来再进行一个实践,在新建一个文件前来判断下文件是否存在。
就是在open()函数中第二个参数中对O_CREAT和O_EXCL进行连用来判断,即

fd=open("newfile", O_RDWR | O_CREAT | O_EXCL, 0777);

如果文件存在,则返回File exists信息。当然判断文件是否存在不只这一种方法。

还有一种操作就是将文件截断为0,使用参数为O_TRUNC。

read函数

read函数的使用格式如下:

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

参数中,fd是文件描述符;buf是一个缓冲区,可以通过新建一个数组并将该参数设置为数组名,或者malloc一个内存块,然后把指针名设置为改参数;count是字节数。

项目 内容
函数功能 在文件描述符上执行读操作
函数描述 read()从文件描述符fd中读取count字节的数据并放入从buf开始的缓冲区。如果count为零,read()返回0并不执行其他操作,如果count大于SSZIE_MAX,则结果不可预料。
返回值 成功时返回读取到的字节数(为0表示读到文件描述符,即文件已经读完了),此返回值受文件剩余字节数限制,当返回值小于指定的字节数时并不意味着错误,这可能因为当前可读取的字节数小于指定的字节数。发生错误时返回-1,并置errno为相应值,在这种情况下无法得知文件偏移位置是否有变化。

write函数

write函数的使用格式如下:

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

参数中,fd是文件描述符;buf是一个缓冲区,可以通过新建一个数组并将该参数设置为数组名,或者malloc一个内存块,然后把指针名设置为改参数;count是字节数。

项目 内容
函数功能 在文件描述符上执行写操作
函数描述 write()向文件描述符fd所引用的文件中写入从buf开始的缓冲区中count字节的数据。POSIX规定,当使用了write()之后在使用read(),那么读取到的应该是更新后的数据。
返回值 成功时返回所写入的字节数(为0表示没有写入数据)。发生错误时返回-1,并置errno为相应值。若count为零,对普通文件没任何影响,但对特殊文件将产生不可预料的后果。

lseek函数

函数功能是用来重新定位文件读写的位移

语法:

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

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

lseek()函数会重新定位被打开文件的位移量,根据参数offset以及whence的组合来决定:

SEEK_SET:
  从文件头部开始偏移offset个字节。
SEEK_CUR:
  从文件当前读写的指针位置开始,增加offset个字节的偏移量。
SEEK_END:
  文件偏移量设置为文件的大小加上偏移量字节。

项目 内容
函数功能 在文件描述符上执行写操作
函数描述 write()向文件描述符fd所引用的文件中写入从buf开始的缓冲区中count字节的数据。POSIX规定,当使用了write()之后在使用read(),那么读取到的应该是更新后的数据。
返回值 成功时返回所写入的字节数(为0表示没有写入数据)。发生错误时返回-1,并置errno为相应值。若count为零,对普通文件没任何影响,但对特殊文件将产生不可预料的后果。
本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: linux 系统函数
最后更新:2019年4月30日

davidcheung

这个人很懒,什么都没留下

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

搜索
欢迎关注我的个人公众号
最新 热点 随机
最新 热点 随机
DEEPFILTERNET:一种基于深度滤波的全频带音频低复杂度语音增强框架 奥地利匈牙利九日游旅程 论文阅读之Study of the General Kalman Filter for Echo Cancellation 小奥看房之鸿荣源珈誉府 杭州往返旅途及西溪喜来登和万怡的体验报告 2022年的第一篇碎碎念
奥地利匈牙利九日游旅程小奥看房之鸿荣源珈誉府论文阅读之Study of the General Kalman Filter for Echo CancellationDEEPFILTERNET:一种基于深度滤波的全频带音频低复杂度语音增强框架
《计算机组成原理(下)》期末考试试题整理 “感动青岛2014”群体奖:2014青岛世界园艺博览会志愿者团队 2010年8月3日-4日凌晨博客更新内容 关于更新github、微信公众号和博客周期的说明 altium学习之常用快捷键(转) 全国大学生英语四六级口语考生手册
标签聚合
linux Java 高中 生活 python学习 鸟哥的linux私房菜 leetcode Python 学习 算法
最近评论
davidcheung 发布于 5 个月前(02月09日) The problem has been fixed. May I ask if you can s...
tk88 发布于 5 个月前(02月07日) Hmm is anyone else having problems with the pictur...
cuicui 发布于 9 个月前(10月20日) :wink:
niming 发布于 10 个月前(09月19日) 同级校友,能刷到太巧了
davidcheung 发布于 2 年前(08月16日) 我得找一下我之前整理的word文档看一下,如果找到了我就更新一下这篇文章。
Nolan 发布于 2 年前(07月25日) 您的笔记非常有帮助。贴图不显示了,可以更新一下吗?
davidcheung 发布于 3 年前(06月19日) 到没有看webrtc的代码。现在主要在看我们公司的代码了。。。只是偶尔看一看webrtc的东西。。。
aobai 发布于 3 年前(03月13日) gain_change_hangover_ 应该是每三个block 只能够调整一次,这样保证每帧...
匿名 发布于 5 年前(12月30日) 烫
小奥 发布于 5 年前(12月12日) webRTC里面的NS本身我记得就是在C++里面呀

COPYRIGHT © 2025 小奥的学习笔记. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

陕ICP备19003234号-1

鲁公网安备37120202000100号