最近断断续续的花了近两个月的业余时间,仔细完整拜读了APUE第2版的前1~16章。获益匪浅 ,对UNIX系统及其编程又有了更深的理解。不愧“独具匠心”四字。以下将我的读书笔记整理出来。
UNIX系统编程指的是用户程序的编写,即运行在用户地址空间的应用程序,书中也描述了一些内核实现的细节。但内核编程基本上是平台相关的,故本书不涉及内核编程部分。
全书所附程序清单代码可以在这里下载。
另外提供了包括Solaris、AIX、FreeBSD、Fedora、Ubuntu等UNIX主机的免费使用,如果没有Linux/UNIX环境,可以在该网站注册帐号并用ssh登录相应机器做上机练习,同时可以更好的理解POSIX标准及不同的UNIX平台下的差异。
第一章 UNIX基础
1、UNIX手册页
UNIX手册页(manpages)是进行系统编程必备的参考工具。手册页通常分为7部分,比较重要的部分包括第1部分系统命令、第2部分系统调用、第3部分库函数、第7部分系统管理工具等。命令(man man)可以查看UNIX手册页的详细介绍。一般资料都采用“条目(部分)”的方式来描述UNIX工具,例如“fork(2)”指系统调用库函数fork,可以使用“man 2 fork”来查看其手册页。
2、系统调用与库函数
系统调用提供了用户程序访问内核的接口,用户程序通过执行系统调用获取内核所管理资源的访问权。执行系统调用时,程序控制权交给内核,内核在进程上下文中运行。
库函数通过包含相应的.h头文件引用。用户程序可以通过调用操作系统提供的库函数间接进行系统调用。如printf库函数,它除了管理缓冲区和格式化文本之外,执行了write系统调用访问文件。库函数也可以由其它程序提供,例如GTK库等。用户也可以自己重新实现库函数接口以替代所提供的库函数功能。
另一方面,系统调用实际上也以库函数的形式直接提供给用户程序,如write、fork等。一般来说man手册页的第二部分为UNIX的系统调用,第三部分为库函数。但对用户程序来说,不必专门区别那些是库函数、哪些是系统调用接口。
对于x86平台的Linux,可以通过/usr/include/asm/unistd_32.h查看其系统调用的接口。
3、文件和目录
UNIX系统将计算机中的大部分资源都抽象为文件的形式,这样我们就可以通过open、read、write等函数去直接访问它们。
文件系统的结构:UNIX系统采用树状结构,全局具有唯一的一个根目录,通过“/”访问,除交换分区外,其它文件系统均挂载在/或其子目录下作为子目录存在。
文件名:除字符“/”与“NUL”外,都可以作为UNIX文件的文件名,但为访问方便起见,一般建议采用可打印字符作为文件名,尽量不要使用shell和其它正则表达式的扩展字符,如*、?、等。
路径名:访问一个文件可使用绝对路径(以/开头)或者相对路径(不以“/”起头的均被认作相对路径)。
目录中的特殊文件“.”和“..”:“/”下的“.”“..”指向它自身,可以使用shell命令“ls -ai”验证这两个特殊文件的用法。
工作目录:每个进程都有一个当前工作目录(cwd, current working directory),用途为解释相对路径。
起始目录(用户家目录home directory):用户登录后的初始目录,由文件/etc/passwd指定
4、输入和输出
文件描述符(file descriptor):用以标识引用一个打开的文件,文件描述符是进程的属性。进程不同值相同的文件描述符引用的可以是不同的文件。
标准输入、标准输出和标准出错:进程创建时自动打开的三个文件描述符,默认为0、1、2,一般通过用宏STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO来引用。
不带缓冲的I/O:标准系统调用open、read、write、lseek、close不使用缓冲区,直接把输入/输出送到内核,书中称为“不带缓冲的I/O”。这里的缓冲位于进程地址空间;
标准I/O:C函数库中的标准I/O函数(定义在)使用缓冲区进行输入/输出、以提高吞吐效率。编程时要注意I/O重定向及fork导致复制地址空间等情况下标准I/O函数对缓冲区的使用情况。
5、程序与进程
程序是一个静态的概念,指存放在存储介质上可被内核通过exec系统调用读入并执行其中指令的文件,可能是二进制也可能是文本形式。UNIX上的程序需要执行权限。
进程是执行中的程序,除了程序本身的代码文本、数据外,进程还包括其它自身拥有的资源,诸如进程id(pid)、打开的文件、地址空间、未决信号集、控制线程等。
6、程序线程
对一个用户进程,内核实际执行调度处理的是它的执行线程。一个进程可以拥有一个或多个执行线程,对进程来说这些线程是并发执行的。进程内的多个线程共享进程资源。
7、异步、并发、通信
这几个术语也应该属于UNIX系统较重要的基本概念的一部分。书中没有用专门的章节进行描述,这里是一个补充。
异步(Asynchronous)指的是对系统资源以不可预测的时间和顺序执行的操作。例如等待外部输入、监听TCP端口等都是异步操作。对于异步操作,需要设计阻塞等待或超时等机制(一般都是基于信号机制实现)。
并发(Concurrency):多个并行的异步进程/线程同时在同一个资源上会合时,产生并发。这时由于各自的异步性,读写等I/O操作可能交错发生乱序,从而出现意外的结果。为使它们的操作被强制的顺序化,需要进行同步处理)。同步处理属于广义上的通信的范畴,有多种同步方式,一般采用锁机制。同步处理还需要注意竞争条件出现的死锁。这是UNIX系统编程中较为复杂、容易出现bug的地方。
通信(Communication):指通过某种机制,在不同实体间交换数据,对UNIX编程来说,发送/接收、通知/响应、输入/输出等都属于比较广义的通信的范畴,狭义点的说法就指数据的发送/接收。UNIX编程的主要通信方式为进程间通信和线程通信。广义的进程间通信包括了信号、管道、IPC对象、套接字。条件变量、取消等操作可以用作线程间的通信(通知)。建议性锁和全局变量也是一种通信机制。不同应用场合的通信使用不同的机制和做法。
8、出错处理
UNIX系统一般通过设置全局变量errno()标识程序执行时发生的错误。在系统所提供的库函数中,除非再次发生错误,否则不会改动errno的值也不会将其清零。errno的取值见手册errno(3)。Errno可以通过下面两个函数转换为对应的错误消息字符串。
#include
char *strerror(int errnum);
此函数将指定的errno错误号转换为字符串并返回这个字符串的指针;
#include
void perror(const char *msg);
此函数执行一个格式化输出,为指定的字符串+冒号+当前的errno值对应的字符串。相当于执行了:
printf ("%s: %s", msg, strerror(errno));
9、UNIX用户和用户标识
正常下,uid唯一标识了一个用户;
系统的用户信息定义在/etc/passwd;
用户的创建、修改、删除通常由系统管理员通过useradd、userdel、usermod等系统命令执行,而不应直接修改/etc/passwd文件;
超级用户(root)的uid为0;
组(group)将一个或多个用户组织起来,以使它们能以指定的相同权限访问资源;
正常下,gid唯一标识了一个组;
gid定义在/etc/group;
组的创建、修改、删除通常由系统管理员通过groupadd、groupdel、groupmod等系统命令执行,而不应直接修改/etc/group文件;
用户可以同时加入多个组,命令groups可以打印出用户所在的组,第一个以外的均为附加组;
10、信号
信号是软件中断机制,作为系统某种事件发生的通知;
进程可以通过调用信号函数,在信号递送到进程时选择对其的处理方式。对信号的处理有三种选择:忽略(丢弃)、按系统的默认方式处理、使用自定义信号捕捉函数来处理;
进程还可以选择在信号递送之前屏蔽之,使之成为未决信号而延后处理;
进程捕捉到信号时将被中断,直到信号处理函数返回时方恢复执行;
11、UNIX系统时间
系统使用两种方式标识时间:
日历时间:指自UTC时间1970年1月1日00:00:00以来流逝的秒数,这个时间以time_t类型保存,这是一个32位整型数。在2038年1月18日将发生溢出错误,例如,在UNIX系统上无法创建一个时间为2038年1月以后的文件(touch -t命令)。目前业界尚未对此采取专门措施。
进程时间。用以衡量进程资源耗时情况,包括实际时钟时间(real time,为一个进程执行开始至今的全部时间)、用户CPU时间(user time,为程序在用户地址空间中执行的时间)、系统CPU时间(system time,为程序执行系统调用后在内核空间中执行的时间)。另外如果进程执行了sleep等调用,睡眠的时间不计算为CPU时间,但计入实际时钟时间。
原文地址 http://blog.chinaunix.net/u/24174/showart_1334818.html
阅读(914) | 评论(1) | 转发(0) |