当前位置: 首页 > news >正文

Linux系统 - 系统编程概念

1 系统调用

系统调用是受控的内核入口,借助于这一机制,进程可以请求内核以自己的名义去执行某些动作。以应用程序编程接口(API)的形式,内核提供有一系列服务供程序访问。这包括创建新进程、执行 I/O,以及为进程间通信创建管道等。

  • 系统调用将处理器从用户态切换到核心态,以便 CPU 访问受到保护的内核内存。
  • 系统调用的组成是固定的,每个系统调用都由一个唯一的数字来标识。(程序通过名称来标识系统调用,对这一编号方案往往一无所知。)
  • 每个系统调用可辅之以一套参数,对用户空间(亦即进程的虚拟地址空间)与内核空间之间(相互)传递的信息加以规范。

从编程角度来看,系统调用与 C 语言函数的调用很相似。然而,在执行系统调用时,其幕后会历经诸多步骤。为说明这点,下面以一个具体的硬件平台—x86-32 为例,按事件发生的顺序对这些步骤加以分析。

  1. 应用程序通过调用 C 语言函数库中的外壳(wrapper)函数,来发起系统调用
  2. 对系统调用中断处理例程来说,外壳函数必须保证所有的系统调用参数可用。通过堆栈,这些参数传入外壳函数,但内核却希望将这些参数置入特定寄存器。因此,外壳函数会将上述参数复制到寄存器。
  3. 由于所有系统调用进入内核的方式相同,内核需要设法区分每个系统调用。为此,外壳函数会将系统调用编号复制到一个特殊的 CPU 寄存器(%eax)中。
  4. 外壳函数执行一条中断机器指令(int 0x80),引发处理器从用户态切换到核心态,并执行系统中断 0x80 的中断矢量所指向的代码。
  5. 为响应中断 0x80,内核会调用 system_call()例程(位于汇编文件 arch/i386/entry.S 中)来处理这次中断,具体如下。
    • 在内核栈中保存寄存器值。
    • 审核系统调用编号的有效性。
    • 以系统调用编号对存放所有调用服务例程的列表(内核变量 sys_call_table)进行索引,发现并调用相应的系统调用服务例程。若系统调用服务例程带有参数,那么将首先检查参数的有效性。例如,会检查地址指向用户空间的内存位置是否有效。随后,该服务例程会执行必要的任务,这可能涉及对特定参数中指定地址处的值进行修改,以及在用户内存和内核内存间传递数据(比如,在 I/O 操作中)。最后,该服务例程会将结果状态返回给 system_call()例程。
    • 从内核栈中恢复各寄存器值,并将系统调用返回值置于栈中。
    • 返回至外壳函数,同时将处理器切换回用户态。
  6. 若系统调用服务例程的返回值表明调用有误,外壳函数会使用该值来设置全局变量 errno。然后,外壳函数会返回到调用程序,并同时返回一个整型值,以表明系统调用是否成功。

下图以系统调用 execve()为例,展示了上文述及事件的发生序列。在 Linux/x86-32 上,execve()的系统调用号为 11(__NR_execve)。因此,在 sys_call_table 向量中,条目 11 包含了该系统调用的服务例程 sys_execve()的地址。(在 Linux 中,系统调用服务例程的命名通常会采取sys_xyz()的形式,其中,xyz()正是所论及的系统调用。)

值得注意的是即便对于一个简单的系统调用,仍要完成相当多的工作,因此系统调用的开销虽小,却也不容忽视。
请添加图片描述
因此,从 C 语言编程的角度来看,调用 C 语言函数库的外壳(wrapper)函数等同于调用相应的系统调用服务例程。为调试程序,或是研究程序的运作机制,可使用 strace 命令,对程序发起的系统调用进行跟踪

2 库函数

一个库函数是构成标准 C 语言函数库的众多库函数之一。库函数的用途多种多样,可用来执行以下任务:打开文件、将时间转换为可读格式,以及进行字符串比较等。

  • 许多库函数(比如,字符串操作函数)不会使用任何系统调用
  • 有些库函数构建于系统调用层之上。例如,库函数 fopen()就利用系统调用 open()来执行打开文件的实际操作。

设计库函数是为了提供比底层系统调用更为方便的调用接口。例如,printf()函数可提供格式化输出和数据缓存功能,而 write()系统调用只能输出字节块。同理,与底层的 brk()系统调用相比,malloc()和 free()函数还执行了各种登记管理工作,内存的释放和分配也因此而容易许多。

3 处理来自系统调用和库函数的错误

几乎每个系统调用和库函数都会返回某类状态值,用以表明调用成功与否。要了解调用是否成功,必须坚持对状态值进行检查。若调用失败,那么必须采取相应行动。至少,程序应该显示错误消息,警示有意想不到的事件发生。
不检查状态值,少敲几个字,认定系统调用或库函数“不可能失败”,不对状态返回值进行检查,这会浪费掉大把的程序调试时间

3.1 处理系统调用错误

每个系统调用的手册页记录有调用可能的返回值,并指出了哪些值表示错误。通常,返回值为-1 表示出错。因此,可使用下列代码对系统调用进行检查:

fd =open(pathname, flags, mode);		/*system call to open a file */
if(fd == -1) {/* Code to handle the error */
}if(close(fd)==-1) {/*Code to handle the error */
}

系统调用失败时,会将全局整型变量 errno 设置为一个正值,以标识具体的错误。程序应#include <errno.h>头文件,该文件提供了对 errno 的声明,以及一组针对各种错误编号而定义的常量。所有这些符号名都以字母 E 打头。在每个手册页内标题为 ERRORS 的章节内,都刊载有一份相应系统调用可能返回的 errno 值列表。以下便是利用 errno 诊断系统调用错误的一个简单示例:

cnt= read(fd, buf, numbytes);
if(cnt ==-1) {if(errno == EINTR)fprintf(stderr, "read was interrupted by a signal\n");else {/* Some other error occurred */}
}

如果调用系统调用和库函数成功,errno 绝不会被重置为 0,故此,该变量值不为 0,可能是之前调用失败造成的。因此,在进行错误检查时,必须坚持首先检查函数的返回值是否表明调用出错,然后再检查 errno 确定错误原因

少数系统调用(比如,getpriority())在调用成功后,也会返回−1。要判断此类系统调用是否发生错误,应在调用前将 errno 置为 0,并在调用后对其进行检查(上述手法同样适用于某些库函数)。

系统调用失败后,常见的做法之一是根据 errno 值打印错误消息。提供库函数 perror()和strerror(),就是出于这一目的。

函数 perror()会打印出其 msg 参数所指向的字符串,紧跟一条与当前 errno 值相对应的消息。

#include <stdio.h>
void perror(const char *msg);

以下是对系统调用错误进行处理的一种简单方式:

fd = open(pathname, flags, mode);
if(fd == -1) {perror("open");exit(EXIT FAILURE);
}

函数 strerror()会针对其 errnum 参数中所给定的错误号,返回相应的错误字符串。

#include <string.h>
char *strerror(int erum);/* Returns pointer to error string corresponding to errnum */

由 strerror()所返回的字符串可以是静态分配的,这意味着后续对 strerror()的调用可能会覆盖该字符串。
若无法识别 errnum 所含的错误编号,则 strerror()会返回“Unknown error nnn.”形式的字符串。在某些其他的实现中,在这种情况下,strerror()会返回 NULL。

3.2 处理来自库函数的错误

不同的库函数在调用发生错误时,返回的数据类型和值也各不相同。从错误处理的角度来说,可将库函数划分为以下几类。

  • 某些库函数返回错误信息的方式与系统调用完全相同——返回值为−1,伴之以 errno 号来表示具体错误。remove()便是其中一例,可使用该函数来删除文件(调用 unlink()系统调用)或目录(调用 rmdir()系统调用)。对此类函数所发生的错误进行诊断,其方式与系统调用完全相同。
  • 某些库函数在出错时会返回 −1 之外的其他值,但仍会设置 errno 来表明具体的出错情况。例如,fopen()在出错时会返回一个 NULL 指针,还会根据出错的具体底层系统调用来设置 errno。函数 perror()和 strerror()都可用来诊断此类错误。
  • 还有些函数根本不使用 errno。对此类函数来说,确定错误存在与否及其起因的方法各不相同,可见诸于相应函数的手册页中,不应使用 errno、perror()或 strerror()来诊断错误。

相关文章:

  • AWS EC2 实例告警的创建与删除
  • 浅解Vue 数据可视化开发建议与速度优化
  • FastAPI 异常处理
  • 传统图像分割方法:阈值分割、Canny检测
  • 大学大模型教学:基于NC数据的全球气象可视化解决方案
  • win32相关(进程相关API)
  • 什么是DevOps的核心目标?它如何解决传统开发与运维之间的冲突?​
  • 好坏质检分类实战(异常数据检测、降维、KNN模型分类、混淆矩阵进行模型评估)
  • 7000字基于 SpringBoot 的 Cosplay 文化展示与交流社区系统设计与实现
  • Excel 统计某个字符串在指定区域出现的次数
  • 有什么excel.js支持IE11,可以显示EXCEL单元格数据,支持单元格合并,边框线,单元格背景
  • 小样本分类新突破:QPT技术详解
  • day 33 python打卡
  • SPSS跨域分类:自监督知识+软模板优化
  • SpringBoot-允许跨域配置
  • 参数/非参数检验和连续/离散/分类等变量类型的关系
  • Rust 开发的一些GUI库
  • Reactor和Proactor
  • React从基础入门到高级实战:React 核心技术 - 错误处理与错误边界:构建稳定的应用
  • React 虚拟dom
  • 网站联盟的收益模式/搜索引擎平台有哪些软件
  • 国务院网站建设标准/企业网站多少钱一年
  • 做宠物网站导航应该写什么字/新乡seo网络推广费用
  • 关于重新建设网站的申请表/指数搜索
  • 网站首页轮播图怎么换/图片外链生成
  • 扬州网站建设myvodo/营销型网站建设模板