文章目录
一、系统调用
什么是系统调用:
由操作系统向应用程序提供的程序接口信息,也叫应用编程接口(Application Programming Interface,API),它是是应用程序与操作系统之间交互的接口。
操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境,使应用程序具有更好的兼容性,为了达到这个目的,内核提供了一系列具备了一定功能的内核函数,通过一组称为系统调用(system call)的接口呈现给用户。
系统调用负责把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,然后将处理结果返回给应用程序。
UNIX/Linux大分部的系统功能是通过系统调用实现的,这些系统调用被封装成了C函数的形式,但它们并不是真正的函数。
标准库函数大部分工作在用户态,一部分函数会使用系统调用进入内核态(fopen/malloc),可以使用 time命令统计程序的在用户态和内核态的运行时间:
time ./<可执行文件>
real 0m0.002s # 总执行时间
user 0m0.000s # 用户态执行时间
sys 0m0.000s # 内核态执行时间
总执行时间 = 用户态执行时间 + 内核态执行时间 + 用户态和内核态切换消耗的时间
strace ./可执行文件名 # 追踪函数的调用过程
用户态到内核态切换途径方式
操作系统一般是通过中断来从用户态切换到内核态的。
中断一般有两个属性,一个是中断号,一个是中断处理程序。不同的中断有不同的中断号,每个中断号都对应了一个中断处理程序。在内核中有一个叫中断向量表的数组来映射这个关系。当中断到来时,cpu会暂停正在执行的代码,根据中断号去中断向量表找出对应的中断处理程序并调用。中断处理程序执行完成后,会继续执行之前的代码。
中断分为硬件中断和软件中断,我们这里说的是软件中断,软件中断通常是一条指令,使用这条指令用户可以手动触发某个中断。中断号是有限的,所有不会用一个中断来对应一个系统调用(系统调用有很多)。Linux2.5之前(具体哪个版本不太清楚)下用int 0x80触发所有的系统调用,那如何区分不同的调用呢?对于每个系统调用都有一个系统调用号,在触发中断之前,会将系统调用号放入到一个固定的寄存器,0x80对应的中断处理程序会读取该寄存器的值,然后决定执行哪个系统调用的代码。
0x80指令会让cpu陷入中断,执行对应的0x80中断处理函数。不过在这之前,cpu还需要进行栈切换
,即就是ESP寄存器的值所指向的栈。
中断处理程序除了系统调用(0x80)还有如除0异常(0x00)、缺页异常(0x14)
等等
主要分为几步:
1.保存各种寄存器
2.根据系统调用号执行对应的系统调用程序,将返回结果存入到eax中
3.恢复各种寄存器
普通函数调用与系统调用的区别:
普通函数调用的步骤:
1、调用者会先把要传递的参数压入栈内存
2、根据函数名也就是函数地址跳转到函数所在的代码段位置
3、接下来从栈内存中弹出参数
4、定义局部变量扩展栈内存并执行函数中的相关代码
5、返回执行结果
6、销毁该函数的栈内存
7、回到调用处继续执行
系统调用的步骤:
1、程序中执行到系统调用位置,触发软件中断机制
1、通过软中断机制进入内核运行状态
2、然后内核负责把参数从用户空间拷贝到内核空间
3、然后根据中断编号再执行相关操作
4、等执行完毕后,再把执行结果内核空间拷贝到用户空间
5、返回到程序再继续执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mCzotxC9-1665314898629)(C:\Users\DELL\Pictures\中断.jpg)]
二、一切皆文件
在UNIX和Linux系统下,把操作系统提供的服务和设备都抽象成了文件,因为这样可以给各种设备控制提供了一个简单而统一的接口,程序完全可以象访问普通磁盘文件一样,访问串行口、网络、打印机或其它设备。
在UNIX和Linux系统下中的文件具有特别重要的意义,因为它在Linux中,(几乎)一切皆文件,大多数情况下只需要使用五个基本系统调用 open/close/read/write/ioctl,即可实现对各种设备的输入和输出,Linux中的任何对象都可以被视为某种特定类型的文件,可以访问文件的方式访问之。
-
- 目录文件
-
- 设备文件
- A. 控制台:/dev/console
- B. 声卡:/dev/audio
- C. 标准输入输出:/dev/tty
- D. 空设备:/dev/null
-
- 普通文件
三、文件描述符
什么是文件描述符:
文件描述符是一种非负的整数,表示一个打开的文件,由系统调用(open/creat)返回,在后续操作文件时可以被内核空间引用,内核默认为每个进程打开三个文件描述符:
-
stdin 0 - 标准输入
-
stdout 1 - 标准输出
-
stderr 2 - 标准出错
在unistd.h中被定义为如下三个宏:
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
一般来说,我们打开文件后占用一个文件描述符3(0、1、2已被标准文件描述符占用),下一个则是4(如果它没被占用的话)…
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
// int open(const char *pathname, int flags);
// 参数:
// - pathname:要打开的文件路径
// - flags:对文件的操作权限设置还有其他的设置
// O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的
// 返回值:返回一个新的文件描述符,如果调用失败,返回-1
// 打开或创建一个文件
int fd1 = open("temp.txt", O_RDONLY | O_CREAT);
if(fd1 == -1) {
perror("open");
return -1;
}
int fd2 = open("temp.txt", O_RDONLY );
if(fd2 == -1) {
perror("open");
return -1;
}
printf("fd1: %d\n", fd1);
printf("fd2: %d\n", fd2);
// 关闭文件描述符
close(fd1);
close(fd2);
return 0;
}
运行结果:
我们观察上述代码可以发现:同一个进程的不同文件描述符可以指向同一个文件。
同时,当我们复制该会话(相当于打开了一个新进程),再运行该程序:
发现fd1 = 3 和 fd2 = 4,这表明:不同进程可以拥有相同的文件描述符。
进而,不同进程的不同文件描述符也可以指向同一个文件。
文件描述符与文件指针:
在Linux系统中打开文件后,内存中(内核区间)就会有一个内核对象,也就是记录该文件相关信息的结构体变量,但内核为了自己的安全不能把它的地址返回给用户,而且由于内核区间和用户区间的原因,返回也无法访问、使用。
而且一个进程可能会同时打开多份文件,所以操作系统就在内核区间创建了一张索引表,表的每一项都有一个指向已打开文件的内核对象,文件描述符就是索引表的主键,如果把索引表看作数组,那么文件描述符就是数组的下标,不同进程之间交换文件描述没有意义。
C语言中使用文件指针代表打开的文件,文件指针指向进程的用户区间中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是文件描述符。
int fileno(FILE *stream);
功能:把文件指针转换成文件描述符
FILE *fdopen(int fd, const char *mode);
功能:把文件描述符转换成文件指针
四、文件的创建与打开
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
功能:打开文件
pathname:文件路径
flags:打开文件的方式
返回值:文件描述符,失败返回负值
int open(const char *pathname, int flags, mode_t mode);
功能:打开或创建文件
pathname:文件路径
flags:打开方式
mode:创建文件时的权限
返回值:文件描述符,失败返回负值
int creat(const char *pathname, mode_t mode);
功能:专门用来创建文件,但基本不使用,因为open函数完全具备它的功能。
注意:open/creat所返回的一定是当前未被使用的,最小文件描述符。
一个进程可以同时打开的文件描述符个数,受limits.h中定义的OPEN_MAX宏的限制,POSIX要求不低于16,传统UNIX是63,现代Linux是255。
flags:
O_APPEND 打开文件后位置指针指向末尾
O_CREAT 文件不存在时创建
O_RDONLY 只读权限
O_WRONLY 只写权限
O_RDWR 读写权限
O_TRUNC 清空文件内容
O_EXCL 如果文件存在则创建失败
O_NOCTTY 若pathname指向控制终端,则不将该终端作为控制终端。
O_NONBLOCK 若pathname指向FIFO/块/字符文件,则该文件的打开及后续操作均为非阻塞模式。
O_SYNC write等待数据和属性,被物理地写入底层硬件后再返回。
O_DSYNC write等待数据,被物理地写入底层硬件后再返回。
O_RSYNC read等待对所访问区域的所有写操作,全部完成后再读取并返回。
O_ASYNC 当文件描述符可读/写时,向调用进程发送SIGIO信号。
mode:
S_IRWXU 00700
S_IRUSR 00400
S_IWUSR 00200
S_IXUSR 00100 //uesr
S_IRWXG 00070
S_IRGRP 00040
S_IWGRP 00020
S_IXGRP 00010 //group
S_IRWXO 00007
S_IROTH 00004
S_IWOTH 00002
S_IXOTH 00001 //other
/*
Linux 文件的 ugo 权限把对文件的访问者划分为三个类别:文件的所有者、组和其他人。所谓的 ugo 就是指 user(也称为 owner)、group 和 other 三个单词的首字母组合。
文件的所有者
文件的所有者一般是创建该文件的用户,对该文件具有完全的权限。在一台允许多个用户访问的 Linux 主机上,可以通过文件的所有者来区分一个文件属于某个用户。当然,一个用户也无权查看或更改其它用户的文件。
文件所属的组
假如有几个用户合作开发同一个项目,如果每个用户只能查看和修改自己创建的文件就太不方便了,也就谈不上什么合作了。所以需要一个机制允许一个用户查看和修改其它用户的文件,此时就用到组的概念的。我们可以创建一个组,然后把需要合作的用户都添加都这个组中。在设置文件的访问权限时,允许这个组中的用户对该文件进行读取和修改。
其他人
如果我想把一个文件共享给系统中的所有用户该怎么办?通过组的方式显然是不合适的,因为需要把系统中的所有用户都添加到一个组中。并且系统中添加了新用户该怎么办,每添加一个新用户就把他添加到这个组中吗?这个问题可以通过其他人的概念解决。在设置文件的访问权限时,允许其他人户对该文件进行读取和修改。
*/
int close (int fd);
功能:关闭文件,成功返回0,失败返回-1
问题1:C语言可以定义重名函数吗?
可以,但需要在不同的作用域下才可以重名。
情况1:在不同的源文件中,static函数可以与普通函数重名。
情况2:在函数内定义的函数可以与普通函数重名。
问题2:系统调用为什么可以重名?
因为系统调用不是真正的函数,而是借助软中断实现的,决定执行哪个系统调用的