APUE 的一些小笔记

本文详细解析了Unix操作系统的基本概念,包括Shell指令、文件I/O操作、文件和目录管理、标准I/O库使用、系统数据文件与信息、进程控制等核心组件及其关键功能。同时介绍了如何有效地进行文件操作、目录管理、I/O流定向和系统数据文件的读取与写入,以及如何利用Unix命令进行高效的任务调度。
摘要由CSDN通过智能技术生成

第一章:Unix 基础知识

1.1. Shell 指令应该也遵循大嘴法则。

1.2. pid_t fork (void) 由父进程调用一次,在父进程和子进程中分别返回一次(父进程非负,子进程0)。

1.3. pid_t waitpid(pid_t pid, int *status, int options) 用于等待子进程终止(防止子进程未结束父进程已经运行完毕)。

1.4. 键盘产生中断的方法:中断键(Delete 或者 ^C)和退出键(^\)。

1.5. time_t 用于UTC 时间,clock_t 用于CPU 时间。

1.6. 时钟时间:进程运行的时间总量;用户CPU 时间:运行用户指令消耗的时间;系统CPU 时间:运行内核调用消耗的时间。所以time 指令的输出中,real > user+sys

1.7. 处理errno 的函数要先保存errno,因为任何函数的调用都可能改变errno。

1.8.

第三章:文件I/O

3.1. int open(const char *pathname, int flags, mode_t mode) 中,O_TRUNC 在O_WRONLY 或者O_RDWR mode 下成功打开才会起作用。(4.2)

3.2. 关于off_t lseek(int fd, off_t offset, int whence):因为某些设备允许负数的偏移量,所以测试返回值的时候应该测试是否等于-1(而不是是否小于0)。

3.3. 偏移量大于文件长度会在文件中形成空洞并增加文件长度。

3.4. 文件系统可能采用预读技术,不过预读会在数组长度到达足够长的时候停止。

3.5. 进程中每个fd 表项都有一个指向文件表的指针,该指针指向的表项包含status,offset 和v-node,这样如果两个独立进程共同打开一个文件,每个进程都有自己的offset。

3.6. 所以lseek 只修改offset,不进行I/O

3.7. 原子操作在多进程情况下可以避免程序因为其他进程干扰而出错,原子操作由多步操作组成,要么不执行,要么一次性执行完毕而不进行进程切换。

3.8. int fcntl(int fd, int cmd, ... /* arg */ ) 中,cmd 为F_GETFL 时,如果要判断返回值是否是O_RDONLY(0)、O_WRONLY(1) 和O_RDWR(2),必需得到返回值和O_ACCMODE(3) 或操作的结果,然后挨个判断。

3.9. Shell 的重定向。例如./my_pro 3 3<>list.tmp 表示在fd 3 上打开文件list.tmp 进行读写(2 == argc)。

3.10. set flag:flags |= user_flag; clear flag:flags&= ~user_flag。

3.11. open ("/dev/fd/0", mode) 等效于dup (0),其中mode 大多数系统上被忽略。/dev/fd/0、/dev/fd/1 和/dev/fd/2 分别是stdin、stdout 和stderr。

3.12. 如果用O_APPEND 模式打开一个文件用于读写,那么lseek 可以对读操作生效,对于写操作无效(仍然遵循O_APPEND)

3.13.

第四章:文件和目录

4.1. #define S_ISFIFO(mode) (((mode) & S_IFMT) ==S_IFIFO)。

4.2. 打开文件,必需对包含文件的目录拥有可执行权限。

4.3. open 函数中使用O_TRUNC 模式,必需对文件拥有权限(3.1)

4.4. 创建文件,必需在要创建的目录中拥有权限执行权限

4.5. 删除文件,必需在包含文件的目录中拥有权限执行权限,但是对文件本身不需要权限权限

4.6. exec* ()执行文件,文件必需是一个拥有执行权限普通文件

4.7. open () 或者creat () 创建的新文件:uid 是进程的uid;gid 可以是进程的gid,也可以是所在目录的gid(优先选择)。

4.8. mode_t umask (mode_t cmask) 返回之前的屏蔽字(这货可没有出错返回)。权限 = 0777 - 屏蔽字

4.9. 用chmod ()/fchmod () 改变文件权限位,进程的uid 必需等于文件uid(或者进程具有超级用户权限)。

4.10. 如果文件(当然也包括目录文件)的粘住位(S_ISVTX)被设置,在对文件所在目录有权限的情况下:文件的拥有者、文件所在目录的拥有者或者超级用户才能删除文件。

4.11. 链接的实际内容(数据块)是所指向文件的完整名字。

4.12. stat 结构只有2 项数据取自目录项:文件名和i 节点编号st_ino,其他都取自i 节点。

4.13. 每个系统都有自己的i 节点编号(i 节点的编号指向同一文件系统的i 节点),所以不能使目录指向另一个文件系统的i 节点(所以ln 指令的硬链接无法跨文件系统)。

4.14. 在同一个文件系统移动文件(就是更换名字)的时候,事实上构造了一个新的目录项指向原先的i 节点,旧目录项的链接则被解除。

4.15. 叶目录(不包含其他目录的目录)i 节点的链接数量总是2:命名该目录的目录项和其中的"."。

4.16. 所以非叶目录i 节点的链接数量总是大于2:命名它的目录项、目录中的"." 和每一个子目录中的".."。

4.17. 只有文件i 节点的链接数到达0,并且没有进程打开文件,文件才能被删除。

4.18. 所以关闭文件的时候,如果打开该文件的进程数是0,其i 节点的链接数也是0,内核就会删除文件。

4.19. 所以unlink () 一个open () 的文件时,不会删除内容,只有程序结束后才会删除内容。这个技巧常常用于程序创建的临时文件,调用open () 后立刻调用unlink (),即使程序崩溃,临时文件也不会留下来。

4.20. 注意:只给出符号链接文件的名字,没有任何函数能够删除链接指向的文件,只能删除链接本身。

4.21. int rename(const char *oldpath, const char *newpath),如果oldpath 是非目录文件,newpath 不能为目录。这种情况下newpath 已经存在则先删除。要求权限。

4.22. 当oldpath 和newpath 都是目录文件的时候,规则和4.21 类似,但是newpath 不能是oldpath 的子目录(否则无法删除)。

4.23. 跟随符号链接的函数:access (), chdir (), chmod (), chown (), creat (), exec (), link (), open (), opendir (), pathconf (), stat (), truncate ()。

4.24. 不跟随符号链接的函数:lchown (), lstat (), readlink (), remove (), rename (), unlink ()。

4.25. 只有root 才能使用link () 构建指向目录的硬链接。

4.26. rmdir () 只能删除一个目录。

4.27. getcwd () 函数的第二个参数必需是PATH_MAX+1(虽然APUE 说这上面标准自相矛盾)。

4.28. 所有有读权限的用户都可以目录,但是只有内核可以目录。(目录的权限执行权限和目录无关,它们分别代表能否在目录中创建和删除文件)

4.29. creat () 的权限设置对已经存在的文件无效,但是函数执行后已经存在的文件的长度会被截断为0。

4.30. 目录和符号链接的长度不可能为0("." 和"..",路径至少有一个字符)。

4.31.

第五章:标准I/O 库

5.1. 流的定向(stream‘s orientation)决定了操作的字符是单字节的还是多字节的,可以用int fwide(FILE *stream, int mode) 来设定流的定向(函数不改变已定向流的定向)。

5.2. 按照标准I/O 惯例:标准出错流是不缓冲的(_IONBF),打开至终端设备的流是行缓冲的(_IOLBF),其他流都是全缓冲的(_IOFBF)。

5.3. getc () 和putc () 可以被实现为宏,作为参数的表达式不能有副作用(自增自减……)。

5.4. ISO C 的标准流操作函数中,控制字符串带"+"的流都有权限。

5.5. 流操作函数 *getc* () 的返回值必需为int,否则会截断EOF。

5.6. 但是如果返回值是EOF,需要调用ferror ()或者feof ()来判断流是出错还是达到末尾。

5.7. ungetc () 并没有真正的将字符压送回流,而是压送回标准I/O 库的流的缓冲区里。

5.8. 用fgets () 取代gets () 可以避免程序被恶意的栈溢出攻击,用fputs () 代替puts () 只需记住自己要处理所有的行尾换行符。

5.9. fread () 和fwrite () 只能处理同一个计算机的二进制流(同一个结构中的成员偏移量可能因为不同系统和编译器而有差异,并且浮点值的二进制格式系统之间可能有差异,况且不能确定目标系统或编译器是big-endian 还是little-endian)。

5.10. ftello () 和fseeko () 遵循POSIX 标准,相应类型off_t;fgetpos () 和fsetpos () 遵循ISO 标准,相应结构类型fpos_t,移植性高。

5.11. vprintf 函数族和vscanf 函数族的参数arg 由stdarg.h 定义(而不是varargs.h)。

5.12. int fileno(FILE *stream) 能够返回一个流的文件描述符哦~

5.13. tmpnam () 如果参数为NULL,路径名会放入静态区并返回指向静态区的指针,但是下次调用会覆盖静态区,所以要深拷贝。

5.14.

第六章:系统数据文件和信息

6.1. 防止一个特定用户登录系统,可以将/etc/passwd 的shell 字段设置为/dev/null 或者/bin/false。

6.2. 组ID 65534 和用户ID 65534 不提供任何特权(只能访问每个用户都能读、写的文件),可以用来设置来宾账户。

6.3. POSIX 标准对于系统数据文件一般有3 个函数:getxxx () 读下一个记录(或打开),setxxx () 反绕文件(或打开),endxxx () 关闭文件。

6.4.

第七章:系统数据文件和信息

7.1. exit () 和_Exit () 是ISO C说明的,而_exit () 是POSIX 说明的。

7.2. 但是exit () 不会立刻进入内核,而是先执行终止处理程序和关闭标准I/O 流等操作。

7.3. 这里你可以用int atexit (void (*function) (void)) 来登记一个终止处理程序,最多登记32 个函数。这些函数在main () return 时调用。(不能被_exit () 和_Exit () 执行,这两个函数直接进入内核)

7.4. 无论根据POSIX C 还是ISO C,main () 都不能有第三个参数(char *envp[]),环境表由extern char **environ 给出地址,访问特定的环境变量应该由getenv () 和putenv () 进行。

7.5. 但是你不要自作聪明的直接操作环境表,增加一个环境变量,环境表之下是程序用堆栈,之上是进程的顶部,第一次增加环境变量的时候要malloc () 一块新的堆空间让环境表整体搬家然后再操作。putenv () 和setenv () 能够自动完成这些操作。

7.6. P187 页(原书)的进程结构。在目标文件角度可以参看《程序员的自我修养》第三章。

7.7. 这条是自己编码总结的:realloc () 远远比APUE 说的IMBA,第一个参数是NULL 的时候,它的作用是malloc ();第二个参数是0 的时候,它的作用是free ()。。。

7.8. goto 只能局部跳转,但是setjmp ()/longjmp () 可不管这一套,如果你愿意可以跳到另一个源代码文件里去。

7.9. 但是要注意:当longjmp ()之后,在内存中的变量具有longjmp () 时的值,在寄存器中的变量具有调用setjmp 的值!

7.10. 所以当longjmp () 之后,auto 变量和 register 变量的值可能让你吃惊不少。其中auto 和register 究竟在寄存器还是内存中要看编译器的优化措施,但是可以肯定的是:volatile 变量、static 变量和全局变量一定在内存中

7.11. 另外习题7.1 的答案我觉得应该是这样的:C 语言约定把返回值扔到寄存器eax 中,printf () 执行后eax 的值是13,随后main 函数返回,由于没有指定返回值并且在这过程中没有其他改变eax 的语句,所以eax 不变,于是main () 结束eax 的值仍然是13。当函数返回时,主调函数查看eax 来得到返回值,那么main () 结束后也是如此,shell 查看eax 来确定返回值。因为这时候eax 是13,所以shell 保存了eax 的值,所以echo $? 指令打印出的数值是13。(已经写程序进行了验证)

第八章:进程控制

8.1. ID 为0 的/sbin/init 被称为交换进程,是所有进程的父进程。

8.2. 关于fork ():fork 函数执行一次,但是返回二次。fork ()后的子进程会获得父进程数据段的副本,并且和父进程共享文本段。所以唯一在程序中区别父进程和子进程的方法是:fork () 在子进程中返回0,而在父进程中返回子进程ID 或者-1(出错时)。

8.3. fork ()也会复制父进程的缓冲区,所以如果父进程中使用了行缓冲的函数(例如printf 函数),把程序的输出写入文件(这时缓冲方式变成全缓冲),printf ()的输出结果会在父进程和子进程中各输出一次。但是printf () 只执行了一次,有2 次输出的原因是全缓冲遇到换行符不刷新,所以当父进程和子进程结束的时候各自刷新缓冲区,缓冲区的数据被写入到文件。

8.4. fork ()会复制父进程的文件描述符,但是不会复制文件表项,描述符指向的文件表项由父进程和子进程共享,这样方便父进程和子进程管理文件状态(例如文件偏移)。

8.5. fork ()后父进程和子进程执行顺序是不确定的,但是vfork ()后一定是子进程先执行。

8.5. 但是vfork ()并不会完全复制父进程的地址空间,因为vfork() 后一般跟exec* ()。

8.6. 所以vfork ()中的操作可能会改变父进程中变量的值。

8.7. 根据8.5,fork () 之后的逻辑依赖于这种执行顺序,就会产生竞争条件。

8.8. 不过这个时候可以通过信号来等待另一个进程执行完毕。

8.9. ××注意:在PATH 中,零长度也代表当前目录(行首的和行尾的":",中间的"::")!,为了系统的安全,我猜没人为了省略一个"./"符号而付出系统安全性的代价,如果你使用Slackware Linux,你可以通过修改/etc/profile 来除掉这个安全隐患。

8.10. exec 函数族的命名

l 代表 list,表示函数接受参数的列表形式;

v 代表vector,表示函数接受参数的矢量形式;

p 代表pathname,表示函数会把filename 转换成pathname;

e 代表environment,表示函数接受envp []。

8.11. exec 函数族的关系

规则1:所有带"l"的函数都会转换list 到vector,然后调用相应形式的、名字带有"v" 的函数。(例如execl -> execv)

规则2:只有execve 是Linux 系统调用,名字带"v"的函数的调用链是execvp ->(try PATH)-> execv ->(use environ)-> execve。

8.12. 设计程序的时候,使用最小特权模型。

8.13. 用参数调用解释器文件时,解释器文件的第一行成为解释器的若干参数,调用解释器文件的参数被相应的后移。

8.14. system () 内部调用了fork (),exec* ()和 waitpid (),并且进行了出错处理和信号处理。

8.15.










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值