开发环境:Ubuntu 14.10 64位(自带 GCC 4.9.1)
一、Unix体系结构
严格意义上来说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。通常将这种软件称为内核。
内核的接口被成为系统调用(SystemCall)。
公用函数库构建在系统调用接口之上,应用程序既可以使用公用函数库,也可以使用系统调用。
二、文件和目录
目录(directory)是一个包含目录项的文件。
目录项中的各个名字称为文件名。理论上,只有斜线(/)和空字符(这里是指\0,和字符串的结束符一样)不能出现在文件名中。
这里和Windows有一些区别,在Windows中,(/\ : * ? “ < >|)都不能出现在文件名中,当你试图用这些字符给文件命名时会有提示,如果想要编写跨平台的程序,可能需要注意这个问题。
创建新目录时会自动创建两个文件名:.(点)和..(点点),分别表示当前目录和父目录。对于根目录,二者的内容是一样的,都是当前目录。
绝对路径(Absolutepathname):以斜线(/)开头的路径名。
相对路径(Relativepathname):不以斜线开头的路径名。
书中图1-3给出了一个ls命令的简单实现,我们可以试试将其改写成递归的形式,也就是可以把所有子目录都打印出来。
#include
#include
#include
// 目录的type宏定义是DT_DIR
void list_dir(const char* dir)
{
DIR* dp = opendir(dir);
if (NULL == dp)
return;
struct dirent* dirp = NULL;
while (true)
{
dirp = readdir(dp);
if (NULL == dirp)
break;
// 一定要过滤掉点和点点,否则会陷入无限循环
if (std::string(dirp->d_name) == "." ||
std::string(dirp->d_name) == "..")
continue;
std::cout << dirp->d_name << ": " << (int)dirp->d_type << std::endl;
if (DT_DIR == dirp->d_type)
{
// 把路径补充完整
std::string nextDir = std::string(dir) + "/" + dirp->d_name;
list_dir(nextDir.c_str());
}
}
closedir(dp);
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
std::cout << "Usage: ls directory_name" << std::endl;
return -1;
}
list_dir(argv[1]);
return 0;
}
三、输入和输出
文件描述符(FileDescriptor):通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。
按惯例,每当运行一个新程序时,所有的shell都为其打开3个文件描述符,标准输入(standardinput)、标准输出(standardoutput)以及标准错误(standarderror)。
问题:图1-4中的代码,BUFFSIZE的值如何影响程序的效率?
四、程序和进程
程序(Program)是一个存储在磁盘上某个目录中的可执行文件。(静态的)
程序的执行实例被称为进程(Process)。(动态的)
fork()函数创建一个新进程。新进程是调用进程的一个副本,我们称调用进程为父进程,新创建的进程为子进程。fork对父进程返回新的子进程的ID(一个非负整数),对子进程返回0(可以通过fork的返回值,来判断当前进程是父进程还是子进程)。因为fork创建一个新进程,所以说它被(父进程)调用一次,但(分别在父进程和子进程中)返回两次。
一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。因为它们能访问同一存储区,所以各线程在访问共享数据时需要采取同步措施以避免不一致性。
五、出错处理
在多线程的环境中,多个线程共享进程地址空间,但每个线程都有属于它自己的局部errno,以避免一个线程干扰另一个线程。
对于errno应注意两条规则:
1、如果没有出错,其值不会被例程清除。因此,仅当函数的返回值指明错误时,才检验其值。
2、任何函数都不会将errno值设置为0,而且在
(在手册中提到:一次成功的调用,也可能会修改errno的值)
六、信号
进程有3种处理信号的方式:
1、忽略信号;
2、按系统默认方式处理;
3、提供一个函数,信号发生时调用该函数,这被称为捕捉该信号;
终端键盘上产生信号的两种方法:
1、中断键(interruptkey):通常是Delete键或Ctrl+ C;
2、退出键(quitkey):通常是Ctrl+ \;
这两种方式都用于中断当前运行的进程。
注意kill函数和kill命令的区别,两者都用于给进程发信号:
kill函数是一个系统调用;
kill命令是一个终端命令,默认信号是SIGTERM;
文档中提到signal函数的行为会随系统版本的不同而不同,推荐使用sigaction函数替代signal函数
使用sigaction改写图1-10的程序
//if (signal(SIGINT, sig_int) == SIG_ERR)
// err_sys("signal error");
struct sigaction myAction;
myAction.sa_handler = sig_int;
if (sigaction(SIGINT, &myAction, NULL) != 0)
err_sys("sigaction error");
--------
课后习题:
1、用ls命令
2、在a.out第一次运行之后,第二次运行之前,系统中创建了两个新的进程852和853
3、因为perror的参数是char*指针类型,加上const表示函数不会(也不应该)修改该指针所指向的值;而strerror的参数是int类型,以拷贝的方式传入,即时函数内修改了这个值,也不会影响实参。