
【Linux 】系统调用
Linux 系统调用
1. 系统调用概述
当前操作系统提供给用户操作系统相关资源的【接口】
- SDK : Soft Development Kits 软件开发工具集
- API : Application Program Interface ,软件开发接口文档,也是函数说明文档。
系统提供针对于系统资源的使用,处理方式,系统资源包括文件,内存,进程,线程,网络资源。。。

可以根据图片分析,系统调用(System Calls) 是对当前系统资源的【隔离和保护机制】。用户可以通过 Application, Shell ,库函数(Library Routines) 操作系统调用相关函数,对内核资源进行合理的调配使用。
2. 案例说明系统调用和库函数调用区别
例如文件操作
- 利用 C 语言库函数方式打开文件使用的函数是
FILE *fopen(const char * filepath, const char * mode)
,如果文件存在得到的是一个针对于当前普通文件的 FILE 类型指针- mode 模式选择对应的有
r w a +
,实际上是利用系统调用函数int open(const char * filepath, int flags)
, flags 参数对应就是当前文件的打开方式,返回值类型是 int 类型,对应当前文件的【文件描述符】用户通过 fopen 打开文件,fopen 利用 open 函数对文件资源进行管理操作,可以认为
- fopen 是餐厅的服务员【Application Shell 库函数】
- open 是后厨 【系统调用 System Calls】
库函数和系统函数实际上是相辅相成,尤其是系统函数是提供给库函数相关资源的底层实现。
3. 用户态和内核态
库函数(调用系统调用),例如 printf,fopen,fread ,fwrite。。。
库函数(不调用系统调用),strcpy strcat strcmp...
用户态对应的是 C/C++ 提供的库函数和系统调用函数
内核态是系统调用函数通过系统对应预留接口,利用内核对系统资源的管理和提供。
库函数没有重载,而系统函数有重载
4. C语言中 IO 操作的底层实现
核心内容
- 每一个文件都有一个唯一的【文件描述符 int 类型】
- 操作文件内容需要利用到文件内容指针
- 操作文件,系统会提供对应的缓冲区,降低程序频繁操作硬盘的时间,提升效率
5. 文件描述符【重点,必会点】
5.1 概述
【重点】在 Linux 系统中,万物皆为文件。任何一个设备对于当前计算机系统而言,都是一个文件,例如键盘,鼠标,屏幕,都是以文件方式在系统中执行相关操作,同时利用 read write 对设备文件进行操作,从而满足对应的功能实现。
任何一个文件对于当前程序在使用时,都会有一个唯一的对应的【文件描述符 File Descriptor fd】,操作系统和当前程序通过文件描述符对当前文件进行相关操作。可以完成 Read 和 Write 操作。
5.2 程序对应的文件描述符
程序编写
执行程序,利用
ps -A | grep a.out
搜索目标可执行程序对应的 PID 【进程号】![]()
切换到目标路径
/proc
对应当前系统执行进程的资源文件夹,根据对应 PID 找到目标程序对应文件夹![]()
当前程序占用的文件描述符
![]()
5.3 任何程序默认占用的文件描述符
- 0
#define STDIN_FILENO 0
标准输入文件描述符- 1
#define STDOUT_FILENO 1
标准输出文件描述符- 2
#define STDERR_FILENO 2
标准错误文件描述符任何一个程序,默认的文件描述符范围是 0 ~ 1023, 已经被系统占用的默认描述符是0 1 2,当前程序打开后续的资源/文件,【对应的最小文件描述符是 3】。
文件在第一次被调用时会分配文件描述符,其他文件再次调用会查看此文件句柄,如果发现已被占用则无法以诸如写操作打开此文件
程序管理文件描述的方式是**【位图】**
当前系统默认进程支持的最大fd个数
5.4 系统管理程序文件描述符的方式
采用方式为【位图】
系统PCB管理进程和进程对应文件描述符
6. 系统调用(System Calls) IO 函数
6.1 打开文件 open
函数文档
- 读取,写入文件内容,并且文件存在,使用两个参数的 open system calls 函数
- 如果文件不存在,需要创建,必须使用三个参数的 open system calls 函数
// 所需头文件如下
#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
:这是一个指向要打开或创建的文件路径名的字符串指针。它可以是绝对路径(如/home/user/test.txt
),也可以是相对路径(如test.txt
)。
flags
:该参数指定了文件的打开方式,它是一个位掩码,可以使用多个标志进行按位或(|
)组合。以下是一些常用的标志:
O_RDONLY
:以只读模式打开文件。O_WRONLY
:以只写模式打开文件。O_RDWR
:以读写模式打开文件。O_CREAT
:如果文件不存在,则创建该文件。当使用这个标志时,需要提供第三个参数mode
来指定新文件的权限。O_EXCL
:与O_CREAT
一起使用时,如果文件已经存在,则open
调用会失败,返回 -1 并设置errno
为EEXIST
。这可以用于确保创建一个新文件。O_TRUNC
:如果文件存在且以写模式打开,则将文件截断为零长度。O_APPEND
:以追加模式打开文件,每次写操作都将数据追加到文件末尾。
mode
:当使用O_CREAT
标志时,需要提供这个参数来指定新文件的权限。权限是通过位掩码来设置的,可以使用一些预定义的常量,如:
S_IRUSR
:用户可读。0400
S_IWUSR
:用户可写。0200
S_IXUSR
:用户可执行。0100
S_IRWXU
: 用户可读可写可执行0700
S_IRGRP
:组可读。0040
S_IWGRP
:组可写。0020
S_IXGRP
:组可执行。0010
S_IRWXG
: 组可读可写可执行0070
S_IROTH
:其他用户可读。0004
S_IWOTH
:其他用户可写。0002
S_IXOTH
:其他用户可执行。0001
S_IRWXO
: 其他可读可写可执行0007
- 以上
mode_t
对应的系统设定相关参数,仅作为参考,实际开发中使用直接利用 8 进制数据描述新建文件权限内容 , 例如0664
0775
内核态中的文件和文件夹在打开时设置的权限存在系统降级
返回值类型:
- 返回是当前对应路径文件成功打开之后的【文件描述符 fd File Descriptor】,文件描述符最小值 3
- 如果打开文件失败,返回 -1
6.2 关闭文件 close
函数文档
#include <unistd.h> int close(int fd);
参数解释:
fd
: 对应当前打开文件的【文件描述符 FD File Descriptor】
返回值类型
- 关闭成功返回 0
- 关闭失败,返回 -1,并且设置
errno
6.3 案例代码
推荐利用终端命令搜索对应程序的进程号,在 /proc/xxx 中查看 fd 文件描述符打开和关闭的情况
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
/*
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int close(int fd);
*/
// 利用 open 函数打开已存在的目标文件,打开方式为只读
int fd = open("./1.txt", O_RDONLY);
// 判断是否打开成功
if (-1 == fd)
{
// perror 打印错误信息
perror("Open target File Failed!");
}
// 展示 FD File Descriptor 文件描述符
printf("fd : %d\n", fd);
// 利用 open 函数创建一个新的文件
/*
当前创建文件要求新文件的权限为 0666 权限,但是创建完成之后,发现当前的
文件对应的权限为 0664 权限
【系统降级】 Linux 系统会根据当前系统的默认要求,对用户申请创建的文件,进行
权限降级操作,文件夹默认权限为 0775 而普通文件默认权限为 0664,后续可以通过
其他操作来修改文件的权限操作。
*/
int fd1 = open("./2.txt", O_CREAT, 0666);
if (-1 == fd1)
{
// perror 打印错误信息
perror("Open target File Failed!");
}
// 展示 FD File Descriptor 文件描述符
printf("fd1 : %d\n", fd1);
// 资源打开,需要关闭操作
int num = 0;
scanf("%d", &num);
close(fd);
scanf("%d", &num);
close(fd1);
scanf("%d", &num);
return 0;
}
6.4 写入数据到文件 write
函数文档
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);
**【注意】要求在使用 open 函数打开目标文件得到对应的文件描述符时,提供给当前文件的打开 flags 中必须有写入文件的权限,要求参数为 **
O_WRONLY O_RDWR O_TRUNC O_APPEND
**参数解释: **
fd
: 当前写入文件的文件描述符
buf
: 写入数据的缓冲数据空间首地址
count
: 写入到当前文件的字节个数
返回值
- 写入数据成功,返回值是实际写入到文件中的字节个数
- 写入失败,返回 -1
案例代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
/*
打开文件,得到对应的文件描述符,同时设置打开方式为 O_WRONLY | O_TRUNC
*/
int fd = open("./2.txt", O_WRONLY | O_TRUNC);
if (-1 == fd)
{
perror("Open File Failed!");
}
printf("当前文件对应的 fd : %d\n", fd);
#if 0
int num = 10;
int ret = write(fd, &num, 4);
#endif
char * str = "欢送研究僧!";
/*
当前数据限制写入字节个数为 100 个字节,当前程序会从指定数据的起始位置
开始写入数据到文件中,不会因为字符串终止标记 \0 结束当前写入操作
要求一般情况下,使用 write 函数,数据是多少,就写入多少。
int ret = write(fd, str, 100);
*/
int ret = write(fd, str, sizeof("欢送研究僧!"));
if (-1 == ret)
{
perror("Write Data Failed!");
}
printf("ret : %d\n", ret);
close(fd);
return 0;
}
6.5 从文件中读取数据 read
函数文档
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
**参数解释: **
fd
: 读取操作对应的文件描述符
buf
: 读取数据临时存储的目标缓冲区内存空间,要求缓冲区空间必须连续,可以是数组,也可以是动态内存申请的空间,通常情况下当前空间采用的数据类型为 char 类型,因为 char 类型占用内存空间为 1 个字节,空间操作灵活度更高
count
: 要求读取的字节个数,要求 buf 缓冲区字节个数 大于等于 count
返回值类型
- 成功:返回实际读取的字节数。若返回值为 0,表示已经到达文件末尾(EOF)。实际读取的字节数可能小于
count
,比如遇到文件末尾、被信号中断或者设备一次只能提供部分数据等情况。- 失败:返回 -1,并设置
errno
来表明具体的错误类型。
**常见的 **
errno
值
EBADF
:fd
不是一个有效的文件描述符,或者该文件描述符没有以可读模式打开。EFAULT
:buf
指向的内存区域不可访问。EINTR
:在读取过程中被信号中断。EIO
:发生输入/输出错误。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DEFAULT_BUFFER_SIZE 64
int main(int argc, char const *argv[])
{
// 打开目标文件,得到对应的文件描述符,同时设定当前打开方式为 O_RDONLY
int fd = open("./1.txt", O_RDONLY);
if (-1 == fd)
{
perror("Open File Failed!");
}
// 申请必要的内存空间,同时进行 memset 擦除操作
char * buf = (char *)malloc(DEFAULT_BUFFER_SIZE);
memset(buf, 0, DEFAULT_BUFFER_SIZE);
ssize_t length = read(fd, buf, 64);
if (0 == length)
{
perror("EOF");
}
else if (-1 == length)
{
perror("Read File Failed!");
}
printf("length : %ld\n", length);
printf("buf : %s\n", buf);
free(buf);
close(fd);
return 0;
}
6.6 使用系统调用完成 cp 命令
功能要求
- 终端执行效果为
./cp 1.txt ./a
,将 1.txt 内容复制到 a 文件夹中,最终效果为在 a 文件夹中存在一个 1.txt 文件,内容和上层文件内容一致。- 利用 main 函数中的 argc 和 argv 参数。如果按照以上要求提供的情况
argc = 3 argv = {"./cp", "1.txt", "./a"}
- 利用 read 和 write 系统调用函数对当前源文件和目标文件进行读取和写入操作。
- open 函数需要根据当前文件特征,选择文件的打开方式和文件权限内容
- 感谢你赐予我前进的力量