第三章 文件操作
3.1.1 目录
文件,除了本身包含的内容以外,它还会有一个名字和一些属性,即“管理信息”,包括文件创建/修改日期和它的访问权限。这些属性被保存在文件inode(节点)中,它是一文件系统中的一个特殊的数据块,它同时还包含文件长度和文件在磁盘上的存放位置。
目录是用于保存其他文件的节点编号和名字的文件。目录文件中的每个数据项都是指向某个文件节点的链接,删除文件名就等于删除与之对应的链接。
删除一个文件时,实质旧删除了该文件对应的目录项,同时指向该文件的链接数减1。该文件 中的数据可能仍然能够通过其他指向同一个文件的链接访问到。如果指向某个文件的链接数(即 ls -l 命令的输出中跟在访问权限后面的那个数字)变为0,就表示该节点以及指向的数据不再被使用,磁盘上的相应位置就会标记为可用空间。
3.2 系统调用和设备驱动程序
操作系统的核心部分是一组设备驱动程序。系统调用ioctl用于提供一些特定硬件设备有关的必要控制。每个驱动程序都定义了它自己的一组ioctl命令。
3.3 库函数
针对输入输出操作直接使用底层系统调用的一个问题是它们的效率非常低,原因
(1)使用系统调用会影响系统的性能。与函数调用相比,系统调用 的开销要大些,因为在执行系统调用时,LINUX必须从运行用户代码切换到执行内核代码,然后再返回用户代码。方法是,在程序中尽量减少系统调用的次数,并且让每次系统调用完成尽可能多的工作。
(2)硬件会限制对底层系统调用一次所能读写的数据块大小。例如,磁带机通常一次能写的数据块长度为10K。所以,如果你试图写的数据量不是10K的整数倍,磁带机还是会以10K为单位,从而在磁带上留下了空隙。
因此为了给设备和磁盘文件提供更高层的接口,LINUX提供了一系列的标准函数库。它们是一些函数构成的集合。比如提供输出缓冲的IO库,你可以高效地写任意长度的数据块库函数 则在数据满足数据块长度要求时安排执行底层系统调用。这就极大降低了系统调用的开销。
3.4底层文件访问
write、read、open、close(注意区分umask和chmod 参数值关系)
lseek 对文件描述符fildes的读写指针进行设置 。
fstat、stat、lstat,返回与撕开文件描述符相关的文件的状态信息,该信息将会到一个buf结构中,buf地址以参数形式传递给fstat。可以查看struct stat结构体的相关变量。
dup、dup2系统调用提供了一种复制文件描述符的方法,使我们能够通过两个或者更多个不同的描述符来访问同一个文件。
3.5标准IO库
在标准IO库中与底层文件描述符对应的是流(stream),它被实现为指向结构的FILE的指针。
fopen: 如果你需要对设备 进行明确的控制,最好使用底层系统调用,因为这可以避免库函数带来的一些潜在问题,如输入、输出缓冲。
LINUX并不像MS-DOS那样区分文本文件和二进制文件 ,UNIX把所有文件都看作为二进制文件。
fread、fwrite、fclose函,都是从指定的数据缓冲区里,不推荐fread和fwrite用于结构化数据,部分原因在于用fwrite写的文件 在不同的计算机体系结构可能不具备可移植性。
fflush、fseek:
fgetc 、getc、getchar区别。getc是fgetc的宏实现,有可能有副作用(即宏的副作用最典型的i++)。而getchar() = getc(stdin),它只限于从标准输入里读一个字符。
fputc、putc、putchar作用基本一样。
fgets、gets函数。fgets把读到的字符写到s指向的字符串里,直到出现下面的某种情况:(1)遇到换行符(2)输入的字符数为n-1(3)到达文件尾。它会把遇到的换行符也传递到接收字符里再加上一个表示结尾的空字节\0。gets对传输字节 没有限制,所以它可能会溢出自己的传输缓冲区。因此应该避免使用它并用fgets来代替。
3.6格式化输入和输出
printf、fprintf、sprintf
scanf、fscanf、sscanf:scanf如果使用%s,但使用时必须小心,它会跳 过起始的空白字符,并且会在字符串里出现的第一个空白字符停下来。此外,如果没有使用字段宽度来限定字符,它能够读取的字符串的长度是没有限制的,所以接收字符串必须有足够的空间来容纳。较好的选择是使用一个字段限制符,或者结合使用fgets和sscanf();
其它流函数:fgetpos,fsetpos,ftell,rewind,freopen,setvbuf,remove。
每个文件流都和一个底层文件描述符相关联。最好不要混用。
3.7文件目录和维护(都是系统调用)
chmod、chown、unlink、link、symlink、mkdir、rmdir、chdir、getcwd。
3.8扫描目录
DIR结构体存放在dirent.h头文件中,struct dirent结构体包括了文件inode节点号,文件的名字
opendir、readdir、telldir、seekdir、closedir
3.10 /proc文件系统
linux提供了一种特殊的文件系统procfs它通常以/proc目录的形式呈现,该目录中包含了许多特殊文件用来对驱动程序和内核信息进行更高层的访问。只要应用程序有正确的访问权限,它们就可以通过读写这些文件来获得信息或设置参数。
第4章 Linux环境
4.1 程序参数
4.1.1 getopt
getopt有如下行为:
1)如果选项有一个关联值,则外部变量optarg指向这个值。
2)如果选项处理完毕,getopt返回-1,特殊参数--将使getopt停止扫描选项。
3)如果一个选项要求有一个关联值(),但用户并未提这个值,getopt返回一个问号?,如果我们将选项字符串的第一个字符设置为冒号:,那么getopt将在用户示提供值的情况下返回 冒号而不是问号
getopt函数实际上把所有非选项参数都集中在一起,即处理完成后,从argv[0]~argv[optind-1]都是有效,从argv[optind]位置开始,到最后结尾都是非选项参数。
三个变量 ,int optind:有效参数与非选项参数的分界线。注意区分非选项参数和无法识别参数区别。
char *optarg:经过一次getopt后,对要求有关联值的选项,将其关联值放入该变量中。
int optopt:无法识别参数值
还有getopt的返回值即该选项值。
4.2 环境变量
UNIX规范为各种应用定义了许多标准环境变量,包括终端类型,默认的浏览器、时区等。可以通过
putenv,和getenv函数来获得。
环境是由一组格式为”名字=值“的字符串组成。getenv函数以给定的名字(如时区、浏览器)搜索环境中的一个字符串,并返回与该名字的相关的值,如果请求的变量不存在,则返回NULL。而putenv是将一个格式为”名字=值“的字符串作为参数,并将该字符串回到当前环境中。
注意:环境仅对程序本身有效,你在程序里做的改变不会反映到外部环境中,这是因为变量的值不会从子进程(你和程序)传播到父进程(shell)。
4.3 时间和日期
#include <time.h>
time_t time(time_t *tloc);它返回的是从纪元开始至今的秒数。如果tloc不是一个空指针,time 函数还会把返回值写入tloc指针指向的位置。
double difftime(time_t time1, time_t time2)返回两个时间值的差值,为什么不直接减是考虑到其可移植性。
struct tm *gmtime(const time_t timeval)相当于把timeval规范化。
struct tm *localtime(const time_t *timeval);返回当地的timeval规范化的值,其实就是考虑时区。
time_t mktime(struct tm* timeptr);功能与上相反,是将规范化的值转换成了time_t值。
4.5 用户信息
UID有它自己的类型-uid_t,它定义在sys/types.h中。它通常只是一个小整数。有些UID是系统预定义的,其他的则是系统管理员在添加新用户时创建的。
#include <sys/types.h>
#include <unistd.h>
uid_t getuid(void) ;返回程序关联的UID
char* getlogin(void);返回 与当前用户关联的登录名
系统文件/etc/passwd包含了一个用户账号数据。它由行组成,每行对应一个用户,包括用户名、加密口令、用户标识符(UID)、组标识符(GID)、全名、家目录和默认shell。
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);
4.6 主机信息
int gethostname(char* name, size_t namelen);函数把机器的网络名写入name字符串。该字符串至少有namelen长(对这句话表示怀疑)。返回0表示成功。
int uname(struct utsanme *name);函数把主机信息写入name参数指向的结构。
4.7 日志
通常这些日志信息被记录在系统文件中,它可能是/usr/adm或/var/log目录中,对一个典型的linux安装文件来说,文件/var/log/messages包含所有的系统信息,/var/log/mail包含来自邮件系统的其它日志信息,/var/log/debug可能包含调试信息。可以通过查看/etc/syslog.conf文件或者/etc/syslog-ng/sys-log-ng.conf文件来检查系统配置。
4.8 资源和限制
#include <sys/resource.h>
int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int priority);
int getrlimit(int resource, struct rlimit *r_limit);
int setrlimit(int resource, const struct rlimit *r_limit);
int getrusage(int who, struct rusage *r_usage);
一个程序耗费的CPU时间可分为用户时间(程序执行自身的指令所耗费的时间)和系统时间(操作系统为程序执行所耗费的时间,即执行输入输出操作的系统调用或其它系统函数所花费的时间)。 getrusage函数将CPU时间信息写入参数r_usage指向的rusage结构中。
默认的优先级是0,正数优先级用于后台任务,它们只在没有其他更高优先级的任务准备运行时才执行。负数优先级使一个程序运行更频繁,获得更多的CPU可用时间。
第5章 终端
5.1对终端进行读写
1.标准模式和非标准模式
默认情况下,只有在用户按下回车键后,程序才能读到终端的输入,在大多数情况下,这是有益的,因为它允许用户使用退格键和删除键来纠正输入中的错误,用户只在对自己在屏幕上看到的内容满意时才会按下回车键把键入的数据传递给程序。
这种处理方式被称为规范模式或标准模式。所有的输入都基于行进行处理,在一个输入行完成前,终端接口负责管理所有的键盘输入,包括退格键。
与标准模式相对的是非标准模式,在这种模式下,应用程序对用户输入字符的处理拥有更大的控制权。
LINUX和UNIX一样,在其内部都是以换行符作为文本行的结束,也就是说,UNIX用一个单独的换行符来表示一行的结束,而其它操作系统如MS-DOS用回车符和换行符两个字符的结合来表示一行的结束。
2.处理重定向输出
#include <unistd.h>
int isatty(int fd)。注意参数是文件描述符,如果fd连接到的是一个终端,那么它就会返回1,否则0
5.2与终端进行对话
如果不希望程序中与用户交互的部分被重定向,但允许其它的输入和输出被重定向,你就需要将与用户交互的部分与stdout、stderr分享开。为此,你可直接对终端进行讯写。由于linux本身是多用户系统,它通常拥有多个终端,这些终端或者是直接连接的,或者是通过网络进行连接的。那么你怎么才能找到要使用的正确的终端呢>
幸运的是,LINUX和UNIX提供了一个特殊设备/dev/tty来解决定个问题,该设备始终是指向当前终端或当前的登录会话。
也就是说你如果直接对/dev/tty操作相当于,直接对终端操作。
5.4 termios结构
终端可按照不同的模式分成如下几组:
输入模式
输出模式
控制模式 控制终端的硬件特性。主要用于串行线连接调制解调器的情况
本地模式 这里最重要的两个标志是ECHO和ICANON。前者的作用是抵制键入字符的回显,而后者是将终端在两个截然不同的接收字符处理模式间进行切换。如果设置了ICANON的标志,就启用标准输入行处理模式,否则,就启用非标准模式。
特殊控制字符 是一些字符组合。
分别对应termios结构中的
struct termios{
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_cc[NCCS];
};
两个函数 gcgetattr(int fd, struct termios* termios_p) \ gcsetattr(int fd, int actions, const struct temios *termios_p);
要注意输入速度和输出速度是分开处理的,需要分别进行处理。
MIN>0和TIME>0可以用来“是按下了ESCAPE键还是一个以ESCAPE键开始的功能组合键”。
5.5 终端的输出
5.5.1 终端的类型
许多unix系统都是通过终端来使用,虽然如今在很多情况下,”终端“可能实际只是PC上运行的一个终端仿真程序或者是窗口环境中的一个终端应用程序比如X11中的XTERM。
通过使用$echo $TERM可以用来查看当前的终端。
5.7虚拟控制台(/dev/ttyN)和5.8伪终端(对应的是/dev/pty) 不太理解