linux 系统编程--系统调用概念,出错处理(一)

铁律

无论何时,只要执行了系统调用,都要检查调用的返回状态以确定是否调用成功!

概念

系统调用是内核入口,借助于这一机制,程序员可以请求内核去执行某些动作,称为应用程序编程接口API。
无论是linux还是windows,这一概念并没有区别。

① 系统调用将处理器从用户态切换到核心态,以便cpu访问受到保护的内核内存。

② 系统调用的组成是固定的,每个系统调用在内部都有一个唯一的数字来标识。

③ 每个系统调用可辅之以一套参数,对用户空间与内核空间传递的信息加以规范。

从编程的角度看,系统调用和c语言函数的调用很相似。但在执行系统调用时,幕后会经历诸多步骤:

① 应用程序通过c语言函数库中的外壳(wrapper)函数,来发起系统调用。

② 对系统调用中断处理来说,外壳函数必须保证所有的系统函数调用参数可用,外壳函数将可用参数复制到寄存器。

③由于所有系统调用进入内核方式相同,因此外壳函数必须将系统调用编号复制到一个特殊的cpu寄存器(%eax)中。

④外壳函数执行一条中断机器指令(int 0x80),引发cpu从用户态切换到核心态,并执行系统中断0x80的中断向量所指向的代码。
教新的x86-32 硬件平台实现了sysenter指令,较之传统的int 0x80中断指令,sysenter指令进入内核的速度更快。
linux 2.6内核及glibc 2.3.2 以后的版本都支持sysenter指令。

⑤为响应中断0x80,内核会调用system_call()例程来处理中断
Ⅰ 在内核栈中保存寄存器值

Ⅱ 审核系统调用编号的有效性

Ⅲ 以系统调用编号对存放所有调用服务例程的列表(sys_call_table)进行索引,发现并调用相应例程。
如果调用带有参数,会首先检查参数的有效性。
执行必要的任务,最后将结果状态返回给system_call()例程

Ⅳ 从内核栈中恢复各寄存器值,并将系统调用返回值置于栈中。
Ⅵ 返回至外壳函数,同时将处理器切换回用户态。

⑥ 如果系统调用的返回值表明有误,外壳函数会使用该值来设置全局变量errno。
然后外壳函数会返回到调用程序,并同时返回一个整型值,以表明系统调用是否成功。
在linux上,系统调用服务遵循的惯例是——调用成功返回正值,调用失败返回负值。

综上可见,即便对于一个简单的系统调用,仍要完成相当多的工作,因此系统调用的开销虽小,却不容忽视。
同时,从C语言编程的角度看,调用C语言函数库的外壳(wrapper)函数等同于调用相应的系统服务。

系统调用出错处理

在linux中,因为外壳函数会使用一个值来设置全局变量errno。
如果调用成功,errno为正值,调用失败,errno为负值。

所以它提供了一个叫做errno.h的头文件描述对应的错误。
errno是一个整型变量,在errno.h的头文件定义一系列字符串常量来描述错误。
毕竟数字不好记忆,因此还提供了两个函数perror()和strerror()来输出错误。

perror的功能是根据全局变量errno的值来获取对应的字符串,再显示出来。
strerror的功能差不多,区别在于它不会直接显示,而是把对应的字符串保存起来返回。
后者比前者更加灵活,还可以对这个字符串进行处理,配合printf函数显示。

#include <stdio.h>
#include <string.h>     //for strerror()

int main()
{
    int tmp ;
    for(tmp = 0; tmp <=256; tmp++)
    {
        printf("errno: %2d\t%s\n",tmp,strerror(tmp));
    }
    return 0;
}

在这里插入图片描述
通常情况下,要使用errno,perror()和strerror()函数,应该包含头文件errno.h。
上面的例子没有用,是因为在 <string.h>中已经包含了errno.h。

errno的值也是随不同操作系统和硬件架构的不同有所变化。
在我的ubuntu里最大errno值是133.超过之后无法识别错误(unknown error nnn),在某些其他实现中,这种情况strerror会返回NULL。

注意,errno保存的是上一次调用失败的值! 所以在实际使用中,必须坚持检查函数的返回值是否表明出错,这个时候才再检查errno确定错误原因。
所以一个常用方法是在一开始把errno设置为零,以免混淆。

而在win10 64位 vs2015平台上,同样的代码提示:
在这里插入图片描述
说strerror不安全,用strerror_s替代。
运行效果:
在这里插入图片描述
在这里插入图片描述
有定义的只有42个,比起linux少得多。其对应的错误号也有细微差别。
以上仅仅是错误的提示,代码中还需要使用exit(EXIT_FAILURE)来真正退出程序。

库函数错误处理

不同的库在调用发生错误时,返回的数据类型和值各不相同。

① 某些库函数返回错误信息的方式与系统调用完全相同。即返回值为-1

② 某些库函数出错会返回-1之外的其他值,当然也会设置errno来表明具体的出错情况。比如fopen()出错返回NULL指针。

③某些库函数根本不使用errno,对此类函数来说,只能去参考相应的手册,不应使用errno,perror()或者strerror()来诊断错误。

发布了257 篇原创文章 · 获赞 87 · 访问量 19万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览