《Unix系统编程手册》第三章系统编程概念学习

系统调用

系统调用是内核提供的受控的内核入口,通过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,并在调用后对其检查

解析数值型命令行参数的函数

#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对应于命令行中相应参数的名称,即如果出现错误,该字符串会出现在打印的错误信息中。函数实现如下:

#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号有什么意义?
答:没太懂就去查了一下,参考链接见参考第二条。

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系统编程手册》上篇
2、https://blog.csdn.net/JohnnyHu90/article/details/50877322

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值