系统调用
系统调用是内核提供的受控的内核入口,通过API的形式,内核提供了一系列服务供程序调用。
系统调用基本特点:
1、系统调用会从用户态切换到核心态,以便CPU访问受到保护的内核内存;
2、系统调用的组成是固定的,每个系统调用都由唯一的数字来标识(程序通过名称标识系统调用);
3、每个系统调用可以使用一套参数,对用户空间和内核空间传递的信息进行规范。
系统调用执行的基本步骤:
1、应用程序通过调用包装后的库函数发起系统调用请求;
2、API函数需要保证传入的参数可用,然后将参数传入到寄存器中;
3、由于系统调用进入内核的方式相同,所以内核需要区分每个系统调用,对此,内核会将系统调用的编号复制到特殊的CPU寄存器中;
4、API函数执行一条中断指令,将用户态切换到核心态,并执行中断所指向的代码;
5、为了响应中断请求,内核会调用system_call()例程来处理中断
6、若系统调用执行结果表明执行出错,API函数会设置errno。
处理中断过程:
1、在内核栈中保存寄存器值;
2、审核系统调用编号的有效性;
3、以系统调用编号对存放的所有调用服务历程的列表进行索引,并调用相应的系统调用服务例程,若有参数,将先检验参数的有效性
处理来自系统调用和库函数的错误
几乎每个系统调用和库函数都会返回某类状态值,用于表明调用成功与否。少数系统函数在调用时不会失败,例如getpid()总能成功返回进程ID,而_exit()总能终止进行,无需对此系统调用的返回值进行检测。
每个系统调用的手册页记录有调用可能的返回值,并指明了哪些值表示错误。系统调用失败时,会将全局整型变量errno设置为一个正值,以标识具体的错误。程序应包含#include<errno.h>头文件。
如果调用系统和库函数成功,errno绝不会被重置为0,如果errno不为0,可能是之前调用失败造成的。因此在错误检查时,必须坚持首先检查函数的返回值是否表明调用出错,然后再检查errno确定错误原因 。此外,少数系统调用在调用成功后,也会返回-1。因此要判断此类系统调用是否发生错误,应在调用前将errno置为0,并在调用后对其检查。
解析数值型命令行参数的函数
c
#include "tlpi_hdr.h"
int getInt(const char *arg, int flags, const char* name);
long getLong(const char *arg, int flags, const char* name);
与atoi()、atol()和strtol()相比,它们针对数值型参数提供了一些基本的有效性检查。它们会将arg指向的字符串转换成int或long,如果不是一个有效的字符串,那么它们会打印一条错误并终止程序。如果name非空,则将该字符串用于标识arg对应于命令行中相应参数的名称,即如果出现错误,该字符串会出现在打印的错误信息中。函数实现如下:
c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>
#include<errno.h>
static void gnFail(const char* fname, const char* msg, const char* arg, const char* name)
{
fprintf(stderr, "%s error", fname);
if(name)fprintf(stderr, " (in %s)", name);
fprintf(stderr, ": %s\n", msg);
if(arg && *arg != '\0')
fprintf(stderr, " offending text: %s\n", arg);
exit(EXIT_FAILURE);
}
static long getNum(const char* fname, const char* arg, int flags, const char* name)
{
if(!arg ||*arg == '\0')
gnFail(fname, "null or empty string", arg, name);
int base = (flags & GN_ANY_BASE) ? 0 : (flags & GN_BASE_8) ? 8 : (flags & GN_BASE_16) ? 16 : 10;
errno = 0;
char* endptr;
long res = strtol(arg, &endptr, base);
if(errno != 0)
gnFail(fname, "strtol() failed", arg, name);
if(*endptr != '\0')
gnFail(fname, "nonnumeris characters", arg, name);
if((flags & GN_NONNEG) && res < 0)
gnFail(fname, "negative value not allowed", arg, name);
if((flags & GN_GT_0) && res <= 0)
gnFail(fname, "value must be > 0", arg, name);
return res;
}
long getLong(const char* arg, int flags, const char* name)
{
return getNum("getLong", arg, flags, name);
}
int getInt(const char* arg, int flags, const char* name)
{
long res = getNum("getIng", arg, flags, name);
if(res >INT_MAX || res < INT_MIN)
gnFail("getInt", "integer out of rangs", arg, name);
return (int)res;
总结
系统调用允许进程向内核请求服务,与用户空间的函数调用相比,哪怕最简单的系统调用都会产生显著的开销,其原因是在执行系统调用时,系统需要切换到核心态。此外内核还需要验证系统调用的参数、用户内存以及内存之间也有数据需要传递
练习
3-1 使用Linux专有的reboot()系统调用重启系统时,必须将第二个参数magic2定义为一组magic号。这些magic号有什么意义?
答:没太懂就去查了一下,参考链接见参考第二条。
cpp
include/linux/reboot.h:#define LINUX_REBOOT_MAGIC1 0xfee1dead
include/linux/reboot.h:#define LINUX_REBOOT_MAGIC2 672274793
include/linux/reboot.h:#define LINUX_REBOOT_MAGIC2A 85072278
include/linux/reboot.h:#define LINUX_REBOOT_MAGIC2B 369367448
include/linux/reboot.h:#define LINUX_REBOOT_MAGIC2C 537993216
Here are the answers.
0xfee1dead ="Feel Dead"
672274793=0x28121969 28/12/1969 is when Linus Torvalds was born.
These are Linus' daughters' birthdays:
85072278= 0x5121996 05/12/1996 is when Patricia Miranda was born.
367369448=0x16041998 16/04/1998 is when Daniela Yolanda was born.
537993216=0x20122000 20/12/2000 is when Celeste Amanda was born
参考
1、《Unix系统编程手册》上篇