UNIX环境高级编程---读书笔记

来源:互联网 发布:服务器数据库恢复 编辑:程序博客网 时间:2024/05/07 13:26

第一章

errno:

(1)当函数出错时会被设置为含有附加信息的整型变量

(2)每个线程有其局部errno以避免一个线程干扰另一个线程

(3)如果没有出错其值不会被清除,只有出错时才需检验,并且任何函数不会把它设置为0

(4)可以使用perror基于其当前值输出一条出错信息

(5)如果出错分为致命性和非致命性,前者可以延迟一段时间重试,后者则执行不可恢复动作:可在终端打印出错信息或写入日志,然后终止。


系统调用与库函数的区别:

(1)库函数可能调用一个或多个系统调用,但它们并不是内核的入口点,有些库函数就不会使用任何系统调用。

(2)可以在必要时替换库函数,但不能替换系统调用。比如sbrk按应用程指定字节增加或减少进程地址空间,而malloc在用户空间管理这块由sbrk分配的空间,能实现一种特定类型的分配。

(3)应用程序可以调用库函数或系统调用,而很多库函数则调用系统调用。

(4)系统调用提供的是一种最小接口,而库函数则提供比较复杂的功能,比如是带上缓冲区。

(5)有相当一部分系统调用时可重入的,比如read write open lseek等,但标准IO库则则是不可重入的,因为它们使用了静态数据结构、或者调用了malloc和free,或者以不可重入的方式使用全局数据结构。

(6)系统调用是线程安全的,因为它对于用户程序来说是原子的;库函数大部分也是线程安全的,但是把多个函数组合在一起不保证线程安全。另外,即便系统调用是安全的,但是系统调用可以通过对内核状态的改变影响其他线程,比如操作文件描述符的系统调用,不用担心多个线程操作同一描述符就导致进程崩溃,但一个在读,另一个线程close了描述符,依然会带来不少问题。

具体实例:http://blog.csdn.net/fycy2010/article/details/46885945


第七章

进程的8种终止方式:

5种正常:从main返回;调用exit;调用_exit _Exit;最后一个线程从其启动例程返回;最后一个线程调用pthread_exit

三种异常:调用abort;接收到一个信号终止;最后一个线程对其取消请求做出响应。


_exit 和exit的区别与联系:

_exit 函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核的各种数据结构;

exit 函数则在这些基础上做了一些小动作,在执行退出之前还加了若干道工序。exit() 函数与 _exit() 函数的最大区别在于exit()函数在调用exit  系统调用前要检查文件的打开情况,把文件缓冲区中的内容写回文件,即所有缓冲的输出数据都被冲洗。

另外,可以通过atexit注册终止处理函数,这些函数是在执行exit时自动调用的  


exit和return的区别  http://blog.chinaunix.net/uid-27007072-id-3285067.html

 主要有几下几个不同点:
  1. return返回函数值,是关键字;exit是一个函数。
  2. return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
  3. return是函数的退出(返回);exit是进程的退出。
  4. return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。
  5. return用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出,非0 为非正常退出。
  6. 非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。
在vfork中和fork中明显看到区别:大家可以尝试一下去敲一下程序,体会一下具体的区别。


环境表:由一个全局变量extern char **environ作为环境指针,指向一个字符指针数组,该数组的最后一个元素是NULL,每个字符指针指向环境字符串。

修改环境表只影响当前进程及调用的任何子进程的环境,不影响父进程环境。注意环境表是原先位于进程地址空间的顶部,如果要修改(并且长度大于原长度)和复制,则需调用malloc将原来位于栈顶之上的环境表移入堆中。P159


C程序的存储空间布局:由低地址到高地址

正文段:存放CPU执行的机器指令,可共享并且只读

初始化数据段:包含程序中需明确赋初值的变量,出现在任何函数之外的声明

非初始化数据段bss:出现在任何函数之外的声明,会被内核初始化为0或空指针

栈:自动变量及每次调用函数时所需保存的信息,如函数的返回地址以及调用者的环境变量

堆:动态存储分配

另外,共享内存一般紧靠在栈之下

注意!程序文件中只有正文段和初始化数据段,而没有未初始化数据段,因为在程序开始运行时它们都被内核置零

堆≈虚拟内存大小-1GB
栈通常为4MB


共享库---动态链接库和静态链接库的区别  http://blog.csdn.net/gamecreating/article/details/5504152


三个动态空间分配函数:

malloc:分配指定字节的存储区,存储区初值不确定

calloc:为指定数量具有指定长度的对象分配空间,每一位初始化为0

realloc:更改以前分配去的长度,可能将以前分配内容移入更大的区域,新增部分初值不确定

它们都会有额外的空间记录管理信息:分配块的长度以及指向下一个分配块的指针。

内存泄露的一个后果:过度的分页开销导致性能下降

关于malloc:

malloc(0):返回一个有效的指针而不是空指针

http://www.cnblogs.com/this-543273659/archive/2011/08/03/2126153.html


setjmp和longjmp:可用于处理发生在深层嵌套函数调用的出错情况,它们本质上实现了非局部的goto,通过在栈上跳过若干调用帧,返回到当前调用路径的某一个函数上。

setjmp:将调用longjmp所用到的栈状态存在一个全局变量jmp_buf中

longjmp:使栈反绕到执行setjmp的时候,丢弃初始状态之后的栈帧

注意这里涉及到了volatile的另一个用处:当使用longjmp时如果不希望回滚自动变量和寄存器的值,可声明为volatile

附:C语言中volatile关键字的作用 http://blog.csdn.net/tigerjibo/article/details/7427366

还可以用在编译过程中由于内存乱序访问带来的逻辑问题,起到类似内存屏障的作用:http://name5566.com/4535.html

应用:用c语言模拟c++的异常处理机制  :http://blog.chinaunix.net/uid-10275706-id-3339018.html


第八章

(1)三个特殊的进程:

ID=0:内核的系统进程,又称交换进程,并不执行任何磁盘上的程序

ID=1:init进程,是一个普通的用户进程,它在内核自举时启动UNIX系统,读取与系统初始化有关的初始化文件,引导到多用户状态,它还托管孤儿子进程成为其父进程

ID=2:页守护进程,用于支持虚拟存储系统的分页操作


(2)fork:子进程得到父进程的数据空间,堆和栈的副本,这些是不共享的,但父子进程共享正文段

实际上在fork时候执行了写时复制:当执行fork时,数据空间,堆和栈由父子进程共享并被内核设置为只读的,若父子进程任何一个试图修改这些区域,内核只为修改区域的那块内存制作一份副本

http://blog.csdn.net/koko7958/article/details/7321691

类似概念还有“读-拷贝-更新” :使用RCU保护的共享数据,读操作不需要获得任何锁就可以访问,不使用原子操作。写操作在访问前需要先复制一份副本,然后对副本进行修改,最后使用一个回调机制,在适当的时机把指向原来数据的指针重新指向新的被修改的数据。RCU可以看做读写锁的高性能版本。


子进程会复制父进程打开的文件描述符,父子进程共享同一个文件表项,使用同一个文件偏移量。

注意几个子进程不会继承自父进程的属性:进程ID,时钟变量,文件锁(但是互斥锁mutex会被继承),未处理的闹钟,未处理的信号集


(3)strlen与sizeof的区别:strlen需进行一次函数调用,运行时计算;sizeof是运算符,在编译时计算缓冲区长度

http://www.cnblogs.com/carekee/articles/1630789.html


(4)每一个进程都有一个父进程,这是通过父进程先于子进程终止,init进程领养实现的。一个父进程已经终止的进程是孤儿进程;一个已经终止,但其父进程尚未对其善后处理(获取终止进程的信息,释放它的占有资源)的进程是僵尸进程;一个由init领养的子进程不会成为僵尸进程,因为只要子进程终止,init就会调用wait取得其终止状态。


(5)wait和waitpid的基本功能是当子进程还在运行时父进程阻塞,当子进程终止时取得其终止状态。后者相比前者的区别:

wait等待的是第一个(任一个)终止的进程,而后者等待指定的进程

waitpid可以非阻塞,而只是取得子进程的状态

waitpid支持作业控制,比如若子进程处于暂停状态,则取得其状态


(6)exec并不创建新的进程,执行前后进程ID不变,它用一个全新的程序替换当前进程的正文,数据,堆和栈


(7)system函数在其实现中调用了fork,exec,waitpid,相比直接只用这几个,它进行了所需的各种错误处理以及信号处理,并且也可以获取到终止状态。


第十五章

(1)管道:半双工,只能在具有公共祖先的进程之间使用

使用举例:在管道线键入由shell执行的命令序列时,shell为每条命令创建一个进程,然后将前一条命令进程的输出用管道与后一条命令的标准输入相连接


(2)FIFO:

可用于不相关进程的数据交换;

它是一种文件类型,所以创建FIFO后一般的文件IO函数如read open write等都可以用于它;

两种应用场景:

FIFO由shell命令使用以便将数据从一条管道传送到另一条,而无需创建临时文件

可用于服务器进程与用户进程的数据传递


消息队列、信号量以及共享存储器都是XSI IPC,它们是基于标识符与键的,即:这些IPC结构在内核中都用一个非负整数的标识符加以引用,从而作为这些IPC对象的内部名,而通过ftok创建键,可以作为外部名,每个IPC对象都与一个键关联,并由内核转换成标识符

优点:可靠、面向记录、流是受控的

缺点更为突出:

1>没有设置访问计数,导致一条消息队列往队列放入信息然后终止,消息队列并未被删除,必须等到又有一个进程读消息或删除队列

对于消息队列,如果删除一条队列会导致仍在使用这一队列的进程在下一次使用它的时候出错返回,这一现象不会出现在文件中

2>由于它们在文件系统中没有名字,所以不能用标准IO库如read write来访问它们或修改特性

3>由于不使用文件描述符,所以就不能使用多路转接IO函数


(3)消息队列:优点是能以非先进先出的顺序取消息,但具有上述三个缺点,并且与别的IPC相比性能不突出,不应再使用它们


(4)信号量:应把它看做是同步原语进程间通信,它是一个计数器,用于多进程对共享数据对象的访问。但是由于其功能较为花哨,即便它的性能比记录锁要好,仍然不建议使用它

其缺点及复杂性体现在:

必须创建信号量集,指定信号量的数量

一个致命的弱点是创建信号量和对其赋值是分开的,而不是原子性的

进程终止时信号量依然留在系统中未被释放

信号量并没有所有权的概念

由于信号量有计数值,而程序中自己的数据结构往往也有计数值,导致同样的信息存两份,从而增加了编程负担和出错可能


优缺点:
信号量(Semaphore)及PV操作
优:PV操作能够实现对临界区的管理要求;实现简单;允许使用它的代码休眠,持有锁的时间可相对较长。
缺:一个信号量只能置一次初值,以后只能对之进行p操作或v操作。
由此也可以看到,信号量机制必须有公共内存,不能用于分布式操作系统,这是它最大的弱点。信号量机制功能强大,但使用时对信号量的操作分散,而且难以控制,读写和维护都很困难。加重了程序员的编码负担;核心操作P-V分散在各用户程序的代码中,不易控制和管理;一旦错误,后果严重,且不易发现和纠正。


(5)共享内存:

由于不需要在客户进程和服务器进程中复制数据,它是最快的IPC

需要用信号量,记录锁等同步原语实现多个进程对同一共享区的同步访问

是一种消息协议(相比于TCP是字节流协议),一个进程填好数据再让另一个进程来读,属于“停等”协议,即便它比TCP socket性能要好一点,用于服务端程序无论是不同机器的进程还是同一台机的,应该都使用TCP socket而不应再引入共享内存来增加编程复杂度

在存储布局中共享内存一般紧靠在栈之下


0 0
原创粉丝点击