资源描述
1 《Linux程序设计》学习笔记01——入门
什么是 Linux
Linux 是一个类 UNIX 内核的可以自由发布的实现版本,是一个操作系统的底层核心。几乎所有为 UNIX 编写的程序都可以在 Linux 上编译运行。
Linux 是由赫尔辛基大学的 Linus Torvalds 开发的,期间得到了因特网上广大 UNIX 程序员的帮助。它最初只是受 Andy Tanenbaum 教授的 Minix (一个小型类 UNIX 系统)启发而开发的个人爱好的程序,但后来逐步发展成为一个拥有自己版权的完整系统。其目的是,保证 Linux 除包含自由发布的代码外,不会集成任何私有代码 。
GNU 项目和自由软件基金会
Linux 社团支持自由软件的概念,即软件本身不应受限,它们应该遵守 GNU 通用公共许可证( GPL )。虽然获得软件可能要支付一定的费用,但此后就可以随意使用,并且它们通常是以源代码的形式发布的。
在 GPL 规则限制下,所有基于这种概念开发的软件都应遵循 GPL 。大家可以在 http://www.gnu.org 上找到更多关于自由软件的概念。
Linux 程序
Linux 应用程序表现为两种特殊类型的文件:可执行文件和脚本文件。可执行文件 是计算机可以直接运行的程序,它们相当于 Windows 中的 exe 文件。脚本文件 是一组指令的集合,这些指令将由另一个程序(解释器,比如 shell 或者 perl )来执行,它们相当于 Windows 中的 bat 文件、 cmd 文件或解释执行的 BASIC 程序。
与 Windows 相比, Linux 程序并不要求可执行程序或脚本具有特殊的文件名或扩展名。当登录 Linux 系统时,我们与一个 shell 程序(通常是 bash )进行交互,它像 Windows 中的命令提示窗口一样运行程序。在当前环境下,必定有一组环境变量与之匹配,其中 PATH 变量 指明了当前可以自动搜索的目录:当需要执行的程序在 PATH 指定的目录中时,你将不需指明待执行程序的全路径(除非有同名程序存在);否则必须指定需要执行程序的路径(相对路径或者绝对路径)。
如果你还是 Linux 环境下的初学者,一定要注意 Linux 使用正斜线( / ) 分割文件名里的目录名,而不是像 Windows 那样使用反斜线( \ )。
建议 :如果你刚刚接触 Linux ,请先学会使用 Linux 再来学习如何在 Linux 环境下编程。
C 语言编译器
这里我们使用 GNU C 编译器,简称为 gcc 。因为它随 Linux 的发行版一起提供,并且它支持 ANSI C 的标准语法。在 http://www.gnu.org 上可以获取 gcc 软件包。
建议 :如果你是初学 C 语言,请编写经典的 HelloWorld 程序来开始你的学习之旅。
开发系统导引
对 Linux 开发人员来说,了解软件工具和开发资源在系统中存放的位置是很重要的。
应用程序 应用程序通常存放在系统为之保留的特定目录中。系统为正常使用提供的程序,包括用于程序开发的工具,都可以在目录 /usr/bin 中找到;系统管理员为某个特定的主机或本地网络添加的程序通常可在目录 /usr/local/bin 或 /opt 中找到。
建议 :在 /usr/local 目录结构下编译、运行自己的程序,并访问必须的文件。
头文件 对 C 语言来说,头文件几乎总是在 /usr/include 目录及其子目录下。那些依赖于特定 Linux 版本的头文件通常可以在目录 /usr/include/sys 和 /usr/include/linux 中找到。
在调用 C 语言编译器时,我们可以使用 -I 标志来包含保存在子目录或非标准位置中的头文件,比如: $ gcc –I/usr/openwin/include fred.c 。它指示编译器不仅在标准位置,也在 /usr/openwin/include 目录中查找 fred.c 中包含的头文件。
提示 :可以使用 grep 命令来搜索包含某些特定定义和函数原型的头文件。
库文件 库是一组预先编译好的函数的集合,这些函数都是按照可以重用的原则编写的。标准库文件一般存储在 /lib 和 /usr/lib 目录中。
库文件的名字总是以 lib 开头,随后的部分指明这是什么库(比如, libm 就代表了数学库)。文件名的最后部分以 . 开始,然后给出库文件的类型: .a 代表传统的静态函数库; .so 代表共享函数库。
提示 :如何编写及使用两种函数库可以参看《精通 UNIX 下 C 语言编程及项目实践》的 学习笔记 1
2 《Linux程序设计》学习笔记02——Shell程序设计
管道和重定向
重定向分为输入重定向 ’<’ 、输出重定向 ’>’ 和附加输出重定向 ’>>’ 。
提示 :默认情况下,如果使用 > 操作符把输出重定向到一个文件而该文件已经存在时,它的内容将被覆盖;如果想改变该默认行为,可以使用 set –C 命令设置 noclobber 选项。
提示 :可以使用 UNIX 的通用“回收站” /dev/null 来有效的丢弃输出信息,比如:
$ kill –l 1234 >/dev/null 2>&1
我们可以使用管道操作符 | 来连接进程。 Linux 和 MS-DOS 不同,在 Linux 下通过管道连接的进程可以同时运行,并且随着数据流在它们之间的传递可以自动地进行协调。举个例子: $ grep –l POSIX* | more
它将输出包含 POSIX 字符串的文件名。实际上,上述命令还有另外两种编写方式:
$ more `grep –l POSIX*` 或者 $ more $(grep –l POSIX*)
Shell 脚本程序
Shell 脚本非常灵活,在 Linux 的使用中往往经常使用它来完成某些繁琐但又必须的工作。鉴于本笔记主要针对 Linux 下的 C 语言编程,这里并不会用太多的篇幅来介绍 Shell 脚本。
运行脚本程序有两个办法:一种是在命令行上直接输入命令 PATH=$PATH:. 或者编辑 .bash_profile 文件,将刚才的命令添加在文件的末尾,然后退出登录再重新登录进来;另一种就是在保存脚本程序的目录中先键入 ./ 再输入脚本命令,如此做的作用是把脚本程序的完整的相对路径告诉 Shell 。
建议 :考虑到系统的安全性,最好的办法是在当前目录中的所有命令前都加上一个 ./ 。
推荐 :如果你想深入地学习使用 Shell ,可以去 ChinaUnix 的 Shell 子论坛 。
Find 命令
只要你是在 UNIX 或者类 UNIX 环境下,一个必不可少的命令就是 find 命令。它的功能是查找文件,比如说,我们在编译某个 C 程序时,发现错误提示说它不识别 STDIN ;那么,很明显的,你的 C 程序缺少某个头文件,但是如何才能知道它是在哪个头文件中定义的呢?一个非常有效地办法就是结合 grep 使用 find 命令:
$ find /usr/include –name “*.h” | xargs grep “STDIN”
上面命令先在 /usr/include 目录下搜索所有包含了 .h 的文件,继而使用 grep 命令对这些头文件查找 STDIN 字符串。当然,你可能说我知道 STDIN 定义在 <stdio.h> ,不用这么麻烦!但在实际的研发工作中,我们经常会在某个并不熟悉的环境(比如设备驱动程序的开发)下编写程序,我们会不可避免的查找某个函数或者宏定义的来源。
提示: find 命令是一个非常复杂的命令。它有很多的选项,如果你想深入的了解它,一个有效地途径就是使用 man 文档。很多情况下,比如不熟悉的命令或者函数都可以从 man 文档中得到解答。
3 《Linux程序设计》学习笔记03——文件操作
在 Linux 中,一切(或几乎一切)都是文件。
文件和设备
硬件设备在 Linux 操作系统中通常被映射为文件。可以使用 mount 命令加载 CD-ROM 、 Windows 下的文件系统或者其他的设备。
UNIX 和 Linux 中比较重要的设备文件有三个:
1. dev/console 该设备代表系统控制台,错误信息和诊断信息通常会被发送到这个设备上。在现代的工作站和 Linux 上,它通常是“活跃”的虚拟控制台;而在 X 窗口系统中,它会是屏幕上一个特殊的控制台窗口。
2. dev/tty 如果一个进程有控制终端的话,那么特殊文件 /dev/tty 就是这个控制终端(键盘、显示屏或者窗口)的别名(逻辑设备)。
注意 :虽然 /dev/console 设备只有一个,但通过 /dev/tty 却能够访问许多不同的物理设备。
3. /dev/null 这是空设备,所有写向该设备的输出都将被丢弃。
提示 :还有一个特殊设备 /dev/zero 经常被用到,它的作用是以内容为 null 字节的源文件来来创建零长度文件。它经常用在 dd 命令的 if 参数中。
扫描目录
与目录操作相关的函数在 dirent.h 头文件中声明。其中,目录的读写函数在《 UNIX 环境下 C 语言编程及项目实践》的 读书笔记 3 中有过介绍。除了 opendir 、 closedir 和 readdir 这三个常见的函数外,还有两个函数: telldir 和 seekdir 。
telldir 函数的返回值记录了一个目录流里的当前位置;接着,我们可以在随后的 seekdir 调用中利用这个值将目录指针重置到之前的位置。
在 Linux 系统上一个常见的问题就是对目录进行扫描,也就是确定一个特定目录下存放的文件。在上述提到的读书笔记中曾给出了一个扫描实例,但是该实例只是扫描了当前目录下的文件,并没有深入到子目录的层面。这里给出一个扫描当前目录下(包含子目录)所有文件的实例 printdir
/*
* function: print all files and directories under 'dir' directory
* paremeters:
* dir [in] point to the directory needed to print
* depth [in] width align to left, incrementing while nested
*/
void printdir(char *dir, int depth){
DIR *dp;
struct dirent *entry;
struct stat statbuf;
if((dp = opendir(dir)) == NULL){
fprintf(stderr, "Can't open direntory: %s\n", dir);
return;
}
chdir(dir);
while((entry = readdir(dp)) != NULL){
lstat(entry->d_name, &statbuf);
if(S_ISDIR(statbuf.st_mode)){
if(strcmp(".", entry->d_name)==0 || strcmp("..", entry->d_name)==0)
continue;
printf("%*s%s/\n", depth, "", entry->d_name);
printdir(entry->d_name, depth+4);
}
else
printf("%*s%s\n", depth, "", entry->d_name);
}
chdir("..");
closedir(dp);
}
提示 :为了在输出时对于不同层次的目录有缩进,这里使用了可变字段宽度 %*s 。其中 * 可以由一个整形值来指定,代表了在输出后面字符串时所要求的宽度。
/proc 文件系统
Linux 提供了一个特殊的文件系统 procfs ,它通常表现为 /proc 目录。该目录中包含了许多特殊文件以允许对驱动和内核信息进行高层访问。只要应用程序有正确的访问权限,它们就可以通过读写这些文件来获得信息或设置参数。
/proc 目录中的文件会随系统的不同而不同,当 Linux 版本中有更多的驱动和设施支持 procfs 文件系统时,该目录中就会包含更多的文件。不过,该目录下有许多东西是在任何 Linux 系统中都存在的。
大多情况下,只需要直接读取这些文件 就可以获取信息。比如 /proc/cpuinfo 、 /proc/meminfo 、 /proc/version 和 /proc/net/sockstat 就分别给出了 CPU 、内存、 Linux 版本和网络套接字的信息。
其实, /proc 目录下的有些文件不但可以读取,还可以修改。比如说,系统中所有运行的程序同时能够打开的文件总数是 Linux 内核的一个参数。它的值可以从 /proc/sys/fs/file-max 文件得到;同样地,你也可以直接修改该文件来更改可以直接打开的文件总数。
提示 :对 /proc 目录中文件进行写操作需要超级用户的权限。在修改数据时一定要小心,写入不当的值很可能会导致严重的后果。
/proc 目录中还有一类文件以数字命名 。比如,当我们使用 ps 命令查看当前正在运行的进程时,会显示每个进程的 PID 。每个进程都会对应 /proc 目录下一个以该 pid 值命名的文件。如果你要查看该进程的具体信息,可以直接读取文件 /proc/(pid) 。在列出的文件中, cmdline 文件会显示该进程由谁启动的;你可以使用 cat 命令或者 od 命令来查看。
4 《Linux程序设计》学习笔记04——Linux环境
当 Linux 编写程序时,必须考虑到程序将在一个多任务环境中运行。这意味着在同一时间会有多个程序运行,它们共享内存、磁盘空间和 CPU 周期等机器资源。甚至同一程序也会有多个实例同时运行。最重要的是,这些程序能够互不干扰,了解他们的环境,并且能正确运行以避免冲突——例如试图与其他程序同时写同一个文件。
程序参数
无论操作系统何时启动新程序,参数 argc 和 argv 都被设置并传递给 main 。这些参数通常由其他程序提供,这个程序一般是 shell ,它请求操作系统启动该新程序。
注意 : Linux 的 shell 一般会在设置 argc 和 argv 之前对文件名参数进行通配符扩展,而 MS-DOS 的 shell 则期望程序接受带通配符的参数并执行它们自己的通配符扩展。
命令行参数 在向程序传递信息方面是很有用的。许多工具程序都是有命令行参数来改变程序的行为或设置选项。这些参数大多以短横线( - )开头;不带后续参数的选项还可在一个短横线后归并到一起。
当需要在自己的程序中处理这些命令行参数时,我们需要自己对这些参数解析从而判断出有效的参数。当参数个数很多时,这一过程将是非常繁琐的。实际上, X/Open 规范定义了命令行选项的标准用法,同时定义了在 C 语言程序中提供命令行开关的标准编程接口: getopt 函数。
getopt 函数 将传递给 main 程序的 argc 和 argv 作为参数,同时接受一个选项指定字符串 optstring ,该字符串高速 getopt 哪些选项可用,以及每个选项是否有关联值。程序将循环调用 getopt 对选项参数进行处理,直到 getopt 返回 -1 时处理完毕。关于 getopt 的详细信息请查阅 man 手册。
我们在实际使用中可能会选择长参数,它以双短横线( -- )表示。 GNU C 库包含了 getopt 的另一个版本,称为 getopt_long ,它能同时接受短参数和长参数。 getopt_long 函数 比 getopt 函数多了两个参数,一个被定义为 option 结构(它指定了函数可以接受的长参数和函数对应返回的值),另一个通常被置 NULL 。
提示 :在使用 getopt_long 函数时,除了要包含头文件 getopt.h 外,还需要把常量 _GNU_SOURCE 一同包含进来。
无参数和 void 参数
在定义自己的程序时,当不需要传递参数时我们可能置参数列表为空或者填入 void 。那么这两种方式相同么?考虑下面两个函数
void foo1();
void foo2(void);
当我们使用 foo1(1,2) 调用 foo1 函数时编译器将不报任何错误,同时可以正常运行;它的执行结果与调用 foo1() 的结果一致。然而,当我们使用 foo2(1,2) 调用 foo2 函数时编译器将报错。因此,无参数和 void 参数使得函数显现出两种不同的行为。
实际上, void 参数指定函数在调用时不能有任何参数;而无参数则没有对参数的传递做任何的规范,也就是说,你可以传递任何类型的任意个数的参数。
环境变量
环境变量是一把双刃剑,使用它的时候要小心!与命令行选项相比,它们对用户来说更加“隐蔽”,这样就使得调试变得更加困难。从某种意义上来说,环境变量就像全局变量一样,它们会改变程序的行为,产生不可预期的结果。
对于环境环境变量的读写操作可以有两种方式,在《 Unix 环境下 C 语言编程及项目实践》的 读书笔记 4 中有详细的介绍。
时间和日期
通常能确定时间和日期对一个程序来说是非常有用的。
我们可以使用 time 函数获取一个 time_t 类型的时间值,该值是从格林威治时间到当前时间点的秒数。函数 localtime 将一个 time_t 类型的时间值转换为 tm 结构,通过该结构可以清晰得了解当前的年、月、日、时、分等。函数 mktime 的功能则相反,它将一个 tm 结构的时间值转换为 time_t 类型。上面的几个函数在《精通 Unix 下 C 语言编程及项目实践》的 读书笔记 5 中有详细的介绍。
为了得到更“友好”的时间和日期表示,像 date 命令输出的那样,我们可以使用 asctime 函数和 ctime 函数。实际上,为了对时间和日期字符串的格式有更多的控制, Linux 和现代的类 UNIX 系统提供了 strftime 和 strptime 函数。它很像是一个针对时间和日期的 sprintf 和 sscanf 函数,具体的信息请查看 man 手册。
注意 :编译包含了 strptime 函数的程序时,需要在包含 time.h 头文件的语句之前包含 _XOPEN_SOURCE 宏定义。
提示 :我们在程序中经常使用 sleep 函数来完成指定时间的睡眠。实际上, sleep 函数只能指定秒级的时间,如果要精确到微妙级 (10 -6 s) 可以使用 usleep 函数 。除此之外,还有一个 timeval 结构 可以完成微妙级的操作。
临时文件
很多情况下,程序会利用一些文件形式的临时存储手段。这些临时文件可能保存着一个计算的中间结果,也可能是关键操作的文件备份。
临时文件的用法很常见,但必须确保应用程序为临时文件选取的文件名是唯一的。 GNU C 提供了两个函数 tmpname 和 tmpfile 来创建临时文件。详细情况请参考 man 手册。
提示 :当需要创建临时文件且需要对其进行读写时,请优先考虑使用 tmpfile 函数。该函数同时创建和打开临时文件,这样就避免了使用 tmpnam 函数时可能有另一个程序用同样的名字打开文件的风险。
用户及主机信息
程序能够通过检查环境变量和读取系统时钟来在很大程度上了解它所处的运行环境。除此之外,程序还可以发现它的使用者的相关信息。
函数 getuid 和 getlogin 分别获取程序关联的 UID 和与该关联 ID 相应的登录名。
实际上,系统文件 /etc/passwd 包含了一个用户账户数据库。要获取某个用户的信息, UNIX 系统并不推荐直接对该系统文件读写,它定义了一组函数来提供一个标准二又有效地获取用户信息的编程接口 getpwuid 和 getpwnam 。它们均返回一个指向与某个用户对应的 passwd 结构指针。
提示 :你可以对程序进行设置,让它们的运行看上去好像是由另一个用户启动的。当一个程序的 SUID 权限被置位时,它的运行就好像是由该可执行文件的属主启动的。一个典型的例子是 su 命令。
在 UNIX 环境下,可以使用 uname 系统调用来获取主机信息。它将主机信息写入一个 utsname 结构。详细细节请查阅 man 手册。
日志
许多应用程序需要记录它们的活动。系统程序经常需要向控制台或日志文件写消息。这些消息能指示错误、警告或是与系统状态有关的一般信息。
提示 :当程序短小时我们经常使用 gdb 工具来调试;但当程序比较庞大时,一个有效地调试手段就是使用日志功能,在日志中搜索出错信息。
UNIX 规范为所有程序提供了一个接口,通过 syslog 函数来产生日志信息。这些信息往往被记录在日志文件 /var/log/message 中,部分调试信息可能记录在 /var/log/debug 中。
通过 syslog 函数向系统的日志文件发送的每条日志信息都有一个优先级。而且不同程序写入的日志信息并不能明显地区分开来。实际上, UNIX 提供了几个函数来改变日志记录行为: openlog 、 closelog 和 setlogmask 函数。有效地使用这三个函数可以在日志文件中与其他程序写入的日志区分开。关于详细信息请查阅 man 手册。
5 《Linux程序设计》学习笔记05——终端
对终端进行读写
在编写程序时,我们往往需要从终端读入数据。一种情况是需要连续地读入用户键入的选择项,这往往出现在数据库程序中。程序员往往会使用 getchar 函数来读取数据,继而判断输入的数据是否有效,从而做出反应。其实如此做带有很大的风险,一个实例程序如下
#include <stdio.h>
char *menu[] = {
"a - add new record",
"d - delete record",
"q - quit",
NULL
};
int getchoice(char *choices[]){
int chosen = 0;
int selected;
char **option;
do {
option = choices;
while(*option){
printf("%s\n", *option);
option++;
}
selected = getchar();
option = choices;
while(*option){
if(option[0] == selected){
chosen = 1;
break;
}
option++;
}
if(!chosen){
printf("Incorrect choice, select again.\n");
}
} while(!chosen);
return selected;
}
int main()
{
int choice = 0;
do{
choice = getchoice(menu);
printf("You choose %c\n", choice);
} while('q' != choice);
exit(0);
}
实例程序中,用户需要键入“ A/ 回车 /Q/ 回车”才能做出选择。但这种处理有着很大的风险,读者可以自己测试一下。这也是初学者经常碰到的问题。
默认情况下,只有当用户按下回车键后,程序才能读到终端的输入。这种处理方式是规范模式或标准模式 。在这种模式下,所有的输入都给予行进行处理,在一个输入行完成前,终端接口负责管理所有的用户键盘输入,包括退格键,应用程序读不到用户输入的任何字符。
与标准模式相对的另一种模式为非标准模式 ,这种模式下,应用程序对用户输入字符的处理拥有更大的控制权。
在上述程序中, Linux 会暂存用户读入的内容,直到用户按下回车键,然后将用户选择的字符及紧随其后的回车符一起传送到程序。所以,每当你选择一个菜单时,程序就调用 getchar 函数处理该字符,而当程序在下一次循环再次调用 getchar 函数时,它会立刻返回一个回车符。一个解决方案是程序在每次读入数据前首先清空回车键之前的所有数据,典型代码如下:
do{
selected = getchar();
} while('\n' != selected);
终端驱动程序和通用终端接口
有时,程序需要更加精细的终端控制能力,而不是仅通过简单的文件操作来完成对终端的一些控制。 Linux 提供了一组编程接口,这使得我们能够控制终端驱动程序的行为,从而允许我们对终端的输入和输出进行更好的控制。
有一组函数调用( GTI )用作控制终端,这组函数调用与用于读写数据的函数是分离的,这就使得读写数据的接口非常简洁,同时又保证用户可以对终端的行为进行更精细的控制。
termios 结构
通过设置 termios 结构中的值和使用一组函数调用,我们就可以对终端接口进行控制。
提示 :使用 termios 结构及相关的函数调用,需要包含 termios.h 头文件;同时需要包含 curses 函数库。
控制终端的操作模式有以下几种:输入模式、输出模式、控制模式、本地模式和特殊的控制字符。具体操作由 tcgetattr 函数和 tcsetattr 函数来完成。其中,本地模式是最常用,也是最重要的一种操作模式。
注意 :程序要将终端设置恢复到程序开始运行之前的状态,这一点是非常重要的。首先保存这些值,然后在程序结束时恢复它们,这永远是程序的职责。
输入模式控制输入数据在被传递给程序之前的处理方式。通过设置 termios 结构中的 c_iflag 成员的标志对它们进行控制。
输出模式控制输出字符的处理方式,即由程序发送出去的字符在传递到串行口或屏幕之前是如何处理的。通过设置 termios 结构中 c_oflag 成员的标志对输出模式进行控制。
控制模式控制终端的硬件特性。通过设置 termios 结构中的 c_cflag 成员的标志对控制模式进行配置。控制模式主要用于串行线连接调制解调器的情况。
本地模式控制终端的各种特性。通过设置 termios 结构中的 c_lflag 成员的标志对本地模式进行配置。其中最常用的两个标志是 ECHO 和 ICANON 。前者抑制键入字符的回显,后者将终端在两个截然不同的接收字符处理模式之间进行切换。如果设置了 ICANON 标志 ,就启用标准输入行处理模式,否则就启动非标准模式。
当用户键入类似 Ctrl-C 这样的组合键时,终端会采取一些特殊的处理方式。 termios 结构中的 c_cc 数组成员将各种特殊的控制字符映射到对应的支持函数。每个字符的位置是由一个宏定义的,但不限制这些字符必须是控制字符。
注意 :在两种不同的模式(标准模式和非标准模式)下, c_cc 数组的下标值有一部分是重叠的。出于这个原因,一定要注意不要将两种模式各自的下标值混淆。
可以通过 stty 命令 查询及修改终端模式。
通过 termios 结构我们还可以控制终端的传入和传出的速度(波特率)。
终端的输出
编写能够应付连接到 UNIX 系统上的各种不同类型终端的程序看上去是一件非常让人畏惧的事情。因为这样的程序必须针对各种类型的终端编写相应的代码。 termifo 软件包的出现解决了这一问题。在绝大多数现代的 UNIX 系统上,这个软件包和另一个软件包 curses 集成在一起。
注意 :在 Linux 系统上,在使用 termifo 软件包时可能需要包含 ncurses 库;该库实现了 curses 软件包的所有功能。
termifo 的功能标识由属性描述,它们被保存在一组编译好的 terminfo 文件中,而这些文件可以方便地在 /usr/lib/terinfo 或 /usr/share/terinfo 目录下找到。例如, VT100 终端的定义就放在文件 /usr/share/terminfo/v/vt100 中。你可以使用 infocmp 程序输出 terminfo 编译数据项的可读版本。
虚拟控制台
在 Linux 的典型安装中将配置 12 个虚拟控制台。虚拟控制台通过字符设备文件 /dev/ttyN 使用, tty 是 Teletype 的缩写,而 N 代表一个数字,从 1 开始。
通过 who 和 ps 命令,可以查看目前登录进系统的用户,以及目前在使用的虚拟控制台及其上运行的 shell 和程序。
Linux 系统一般在前六个虚拟控制台上运行一个 getty 进程,这样用户即可用同一个屏幕、键盘和鼠标在六个不同的虚拟控制台上登录。可以通过组合键 Ctrl+Alt+F<N> 在这六个不同的虚拟控制台之间进行切换。
如果 Linux 系统使用的是图形登录界面或者使用 startx 切入图形界面, X 视窗系统将使用第一个未使用的控制台,通常是 /dev/tty7 。
而伪终端 由字符设备文件 /dev/pty 使用,其中 pty 是 pseudo tty 的缩写。它与 tty 终端的区别在于伪终端没有对于的硬件设备。
运程登录的终端 由字符设备文件 /dev/pts/N 使用。
6 《Linux程序设计》学习笔记06——curses函数库
Curses 标准作为过渡,位于简单的文本行程序和完全图形化界面(一般也更难于编程)的 X 视窗系统程序(如 GTK/GNOME 和 Qt/KDE )之间。
Curses 函数库的名称来自它所提供的功能,它能够优化光标的移动并减少需要对屏幕进行的刷新,因此它也减少了必须向字符终端发送的字符数目。
基本使用方法
Curses 例程工作在屏幕、窗口和子窗口上。所谓“屏幕”就是正在写的设备(通常是终端屏幕,也有可能是 xterm 屏幕)。 Curses 函数库使用两个数据结构来映射终端屏幕,它们是 stdscr 和 curscr 。其中 stdscr 数据结构对应的是“标准屏幕”,它的工作原理和 stdio 函数库中的标准输出 stdout 非常相似,它是 curses 程序中的默认输出插口;而 curscr 数据结构和 stdscr 相似,但它对应的是当前屏幕的样子。
一个使用 curses 函数库的典型例程如下:
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>
int main()
{
initscr();
move(5,15);
printw("%s", "Hello World!");
refresh();
endwin();
exit(0);
}
当对使用 curses 函数库的程序进行编译时,必须在程序中包含头文件 curses.h ,它是需要在编译命令行中用 -lcurses 选项对 curses 函数库进行链接。
从上面的程序可以看到,所有 curses 程序必须以初始化函数 initscr 开始,以函数 endwin 结束。函数 initscr 在一个程序中只能调用一次。
提示 :我们可以先调用 endwin 函数退出 curses ,然后通过调研 clrearok(strscr,1) 和 refresh 函数继续 curses 操作。这样,实际上是首先让 curses 忘记物理屏幕的样子,然后强迫它执行一次完整的屏幕原文重现。
函数 mo
展开阅读全文