本节书摘来自异步社区《Linux 高级程序设计(第三版)》一书中的第1章,第1.2节,作者:杨宗德 , 吕光宏 , 刘雍著,更多章节内容可以访问云栖社区“异步社区”公众号查看
1.2 Linux开发初步
Linux 高级程序设计(第三版)
1.2.1 Linux下C程序标准
在Linux操作系统下进行C程序开发的标准主要有两个:ANSI C标准和POSIX标准。
ANSI C标准是ANSI(美国国家标准局)于1989年制定的C语言标准,后来被ISO(国际标准化组织)接受为标准,因此也称为ISO C。
POSIX标准是最初由IEEE开发的标准族,部分已经被ISO接受为国际标准。
1.ANSI C
ANSI C的目标是为各种操作系统上的C程序提供可移植性保证(例如Linux与Windows之间),而不仅仅限于类UNIX系统。该标准不仅定义了C编程语言的语法和语义,而且还定义了一个标准库。ISO C标准定义的头文件如表1-1所示。
2.POSIX标准
POSIX.1和POSIX.2分别定义了兼容操作系统的C语言系统接口以及工具标准。Posix是类UNIX系统都遵循的标准,例如Ubuntu下和RedHat下没有代码不需要移植可直接编译后执行,而在Linux下基于Posix标准完成的代码就无法在Windows下直接编译并执行。表1-2所示为26项POSIX标准定义的头文件,表1-3所示为26项POSIX标准定义的XSI扩展头文件,表1-4所示为8项POSIX标准定义的可选头文件。
1.2.2 库函数和系统调用
1.系统调用函数和库函数
库函数用以完成常见的特定功能,通常由某一个组织制作发布,并形成一定的标准,可以应用于不同的平台而不需要做任何修改即具有极好的移植性。例如,C函数库能够被绝大多数C编译器支持。
系统调用函数一般与操作系统相关,不同的操作系统所使用的系统调用可能不同。一般来说,如果两个操作系统差异很大,系统调用函数的可移植性就不高。例如Windows采用了系统调用的应用程序不能直接在Linux下编译运行。系统调用函数很多情况下需要访问系统特殊资源,在Linux下,系统调用采用软中断来实现,使用系统调用时,该程序的状态将从用户态切换到内核态。图1-1所示为Linux函数库调用和系统调用示意图。
库函数在实现最终都需要使用系统调用,但它封装了系统调用的部分操作,用户不必关心它使用了哪些系统调用。另外,做上层应用程序开发时也没有必要深入研究系统调用函数的具体实现过程。
2.glibc函数库
众所周知,C语言并没有为常见的操作,例如输入/输出、内存管理等提供内置的支持。这些功能一般由标准的函数库来提供。GNU的C函数库glibc是Linux最重要的函数库,它定义了ISO C标准指定的所有库函数,以及由POSIX或其他UNIX操作系统变种指定的附加特色,还包括与GNU系统相关的扩展。目前,Linux系统大多使用glibc 2.3以上的版本。glibc基于如下标准。
(1)ISO C。C编程语言的国际标准,即ANSI C。
(2)POSIX。GNU C函数库实现了ISO/IEC 9945-1:1996(POSIX系统应用程序编程接口,即POSIX.1)指定的所有函数。该标准是对ISO C的扩展,包括文件系统接口原语、设备相关的终端控制函数以及进程控制函数。同时,GUN C函数库还支持部分由ISO/IEC 9945-2:1993(POSIX Shell和工具标准,即POSIX.2)指定的函数,其中包括用于处理正则表达式和模式匹配的函数。
(3)Berkeley UNIX(BSD和SunOS)。GNU C函数库定义了某些UNIX版本中尚未标准化的函数,尤其是4.2 BSD,4.3 BSD,4.4 BSD UNIX系统(即Berkeley UNIX)以及SunOS(流行的4.2 BSD变种,其中包含某些UNIX System V的功能)。BSD函数包括符号连接、select函数、BSD号处理函数以及套接字等。
(4) SVID(System V的接口描述)。GNU C函数库定义了大多数由SVID指定而未被ISO C和POSIX标准指定的函数。来自System V的支持函数包括进程间通信和共享内存、hsearch和drand48函数族、fmtmsg以及一些数学函数。
(5)XPG(X/Open可移植性指南)。GNU C函数库遵循X/Open可移植性指南(Issue 4.2)以及所有的XSI(X/Open系统接口)兼容系统的扩展,同时也遵循所有的X/Open UNIX扩展。
3.系统调用
系统调用是操作系统提供给外部程序的接口。在C语言中,操作系统的系统调用一般通过函数调用的形式完成。因为这些函数封装了系统调用的细节,将系统调用的入口、参数以及返回值用C语言的函数调用过程实现。在Linux系统中,系统调用函数定义在glibc中。系统调用需要注意以下几点。
(1)系统调用函数通常在成功时返回0值,不成功时返回非零值。如果要检查失败原因,则要判断全局变量errno的值,errno中包含错误代码。
(2)许多系统调用的返回数据通常通过引用参数传递。这时,需要在函数参数中传递缓冲区地址,而返回的数据就保存在该缓冲区中。
(3)不能认为系统调用函数比其他函数的执行效率高。因为系统调用是一个非常耗时的过程。
1.2.3 在线文档介绍
Linux是免费的自由软件操作系统,大量的应用程序都是由自由组织和个人编写,没有特别大型的公司维护。因此,在程序中出现的大量问题都需要程序开发人员自己解决。一般的解决方案是查看该程序的帮助文件,因为Linux开发人员在发布其个人程序时都添加了详细的帮助文件。在Linux系统下,常用的在线帮助文件有man、info以及HOW-TO等。最常用的是man手册。
1.man手册
man即manual,是UNIX系统手册的电子版本。根据习惯,UNIX系统手册通常分为不同的部分(或小节,即section),每个小节阐述不同的系统内容。目前的小节划分如下。
man 1:命令,例如ls。可以查看shell终端下命令使用介绍。
man 2:系统调用。可以查看内核系统调用函数的描述,以及参数和返回值情况。
man 3:函数库调用。可以查看普通函数库中的函数。
man 4:特殊文件。可以查看/dev目录中的特殊文件。
man 5:文件格式和约定。可以查看/etc/passwd等文件的格式,例如man /etc/passwd。
man 6:游戏。
man 7:杂项和约定。标准文件系统布局、手册页结构等杂项内容。
man 8:系统管理命令。只有管理员使用的命令。
man 9:内核例程。非标准的手册小节,便于Linux内核的开发而包含其他手册小节。
例如,常用命令行:
man 2 open //查看open函数帮助
man 7 man //在第7部分查看man功能
2.info手册
Linux中的大多数软件开发工具都来自自由软件基金会的GNU项目,这些工具软件的在线文档都以info文件的形式存在。info程序是GNU的超文本帮助系统。info文档一般保存在/usr/info目录下,使用info命令可以查看info文档。要运行“info”,可以在shell提示符后输入“info”,也可以在GNU的emacs中键入“Esc-x info”。
info帮助系统的初始屏幕显示为一个主题目录,可以将光标移动到带有“*”的主题菜单上面,然后按回车键进入该主题,也可以键入“m主题菜单的名称”进入该主题。例如,你可以键入“m”,然后再键入“gcc”进入gcc主题中。如果要在主题之间跳转,则必须记住如下几个命令键。
n:跳转到该节点的下一个节点。
p:跳转到该节点的上一个节点。
m:指定菜单名。
f:进入交叉引用主题。
l:进入该窗口中的最后一个节点。
TAB:跳转到该窗口的下一个超文本链接。
RET:进入光标处的超文本链接。
u:转到上一级主题。
d:回到info的初始节点目录。
h:跳出info教程。
q:退出info。
3.HOW-TO
可供用户参考的联机文档的另一种形式是HOWTO文件,这些文件位于系统的/usr/doc/HOWTO目录下。HOWTO文件的文件名都有-HOWTO后缀,并且都是文本文件。每一个HOWTO文件包含Linux某一方面的信息,例如支持硬件或如何建立引导盘。要想查看这些文件,进入/usr/doc/HOWTO目录,使用more命令,具体形式如下:
$ cd /usr/doc/HOWTO;more topic-name-HOWTO //切换到该目录
另外,还有其他格式的HOWTO文档,例如HTML和PS等,保存在/usr/doc/HOWTO/ other-formats下。
4.其他
Linux的内核文档一般包含在内核源代码目录/usr/src/'uname –r'/Documentation/usr/doc中,该目录下包含有大量与特定软件或函数库相关的说明性文档。
1.2.4 获取错误信息
调用库函数或系统调用函数后,如果执行成功将返回0或者正确值;如果执行失败,返回 − 1,并把系统全局变量errno赋值,以指示具体的错误情况。该变量在文件errno.h头文件中被声明,具体如下所示:
#ifndef errno
extern int errno;
#endif
所有的错误代码都在errno.h文件中定义。以下是/usr/include/asm/errno.h文件部分内容:
come from /usr/include/asm/errno.h
#ifndef _I386_ERRNO_H
#define _I386_ERRNO_H
#define EPERM 1 /* Operation not permitted */ //没有操作权限
#define ENOENT 2 /* No such file or directory */ //文件和目录不存在
#define ESRCH 3 /* No such process */ //没有此程序
#define EINTR 4 /* Interrupted system call */ //系统调用中断
#define EIO 5 /* I/O error */ //I/O错误
……
为了打印出具体的errno值所对应的错误提示信息,一般使用perror函数。此函数声明如下:
//come from /usr/include/stdio.h
//Print a message describing the meaning of the value of errno. //打印错误值的具体信息描述
extern void perror (__const char *__s); //perror函数声明
另外,Linux系统还提供了以下错误处理函数:
#include <string.h> //string.h头文件中
char *strerror(int errnum); //strerror函数声明
#include <errno.h> //errno.h头文件中
extern char *sys_errlist[ ]; //sys_errlist函数声明
extern int sys_nerr; //sys_nerr函数声明
如果在应用程序中使用系统调用出错后使用perror(),可将错误相关的消息写入到标准错误输出,描述调用系统函数或库函数期间遇到的最后一个错误。perror()函数首先输出参数字符串s,后接冒号、空格、消息和换行符。为发挥它最大的作用,参数字符串应包括导致错误的程序的名称。如下示例所示:
if (chmod("test02", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
{
perror("stat");
exit(EXIT_FAILURE);
}
如果上例的chmod()函数调用失败,将打印“stat: 具体的errno message”错误信息。
错误编号取自符号errno,出错时将设置此符号,但执行无错误调用后不会清除此符号。消息的内容与将errno用作参数的strerror()函数返回的内容相同。如果给定了一个NULL字符串,则perror()函数只输出消息和换行符。