一、系统编程概念

1、系统调用

内核提供的一套供应用程序调用的服务。包括创建新进程、执行I/O,以及为进程间通信创建管道等等。

●系统调用将处理器从用户态切换到核心态,以便CPU访问收到保护的内核内存

●每个系统调用都由一个唯一的数字来标识,而程序通过名称来标识系统调用,对这一数字编号往往一无所知

●每个系统调用都可以配上一套参数来对用户空间与内核空间之间传递信息加以规范

 

系统调用的步骤:

(1)通过C函数库的外壳(wrapper)函数,来发起系统调用;

(2)外壳函数将传入的参数复制到寄存器。将系统编号复制到特殊的CPU寄存器(%eax)中;

(3)执行一条终端指令int 0x80(128),使处理器从用户态切换到核心态,并执行这个矢量中断所指向的代码(2.6以后实现了sysenter指令,进入内核速度更快),调用system_call()例程来处理这次中断;

(4)若系统调用服务例程的返回值表明调用有误,外壳函数会使用该值来设置全局变量errno。然后外壳函数会返回到调用程序,同时返回一个整型值来表明系统调用是否成功。(linux中一般返回一个非负值标识调用成功,发生错误时取反常量errno,返回一个负值,然后C函数库对errno再次取反,将结果拷贝到errno,返回-1)

 

2、库函数和glibc

库函数:底层系统调用的包装。

glibc(GUN C语言函数库):linux下C语言函数库的一个实现版本

●确定系统的glibc版本:(a)直接执行/lib/libc.so.6(可通过运行ldd my prog | grep libc来找到其具体目录,ldd命令列出动态依赖性);(b)测试常量__GLIBC__和__GLIBC__MINOR__的值;(c)调用#include <gnu/libc-version.h>的const char *gnu_get_libc_version(void);函数来获取;

 

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

(1)处理系统调用的错误

示例:

cnt = read(fd, buf, numbytes);

if (cnt == -1){

  if (errno == EINTR)

    fprint(stderr, "read was interrupted by a signal\n");

  else {

    /* Some other error occurred */

  }

}

系统调用失败后,常常根据errno值来打印错误消息。这个功能提供库函数:(a)stdio.h:void perror(const char *msg);(b)string.h:char * stderror(int errnum);

(2)处理库函数的错误:

某些库函数返回的错误信息和系统调用完全相同,比如remove();

某些库函数出错时会返回-1以外的值,但仍然会设置errno值。比如fopen()出错会返回一个NULL指针,会根据具体的系统调用来设置error值,可以是哦那个perror()和stderror();

还有些库函数根本不使用errno值,需要查看相关手册。

 

4、可移植性问题

(1)特性测试宏

系统调用和库函数API的行为受到一些标准制约,这些标准一部分是由Open Group(SUS)标准机构来制定的,另一部分是由BSD和System V Release4这样的重要的unix实现来定义的。编写可移植性应用程序的时候,有时会希望各个头文件只显露遵循特定标准的定义。要达到这一目的,需要引入特征测试宏。

(2)系统数据类型

为了增加程序可移植性而通过typedef引入的数据类型。比如size_t,pid_t,socklen_t,fd_set等等。

(3)其它可移植性问题:

●初始化操作和使用结构

●使用未见诸于所有实现的宏

●不同实现之间的所需头文件的变化

 

posted on 2018-07-31 01:24  Kidyy  阅读(101)  评论(0)    收藏  举报

导航