APUE(3rd) 学习笔记:01:第一章:UNIX基础知识

一个简单的学习笔记,主要用于记录书中的要点,自己的理解&疑问,以及课后习题;最重要的是,以此作为自我监督的手段,督促自己不要半途而废。

开发环境:Ubuntu 14.10 64自带 GCC 4.9.1


一、Unix体系结构

严格意义上来说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。通常将这种软件称为内核。

内核的接口被成为系统调用(SystemCall)。

公用函数库构建在系统调用接口之上,应用程序既可以使用公用函数库,也可以使用系统调用。

APUE(3rd) 学习笔记:01:第一章:UNIX基础知识_第1张图片


二、文件和目录

目录(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,而且在中定义的所有常量都不为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第一次运行之后,第二次运行之前,系统中创建了两个新的进程852853

3、因为perror的参数是char*指针类型,加上const表示函数不会(也不应该)修改该指针所指向的值;而strerror的参数是int类型,以拷贝的方式传入,即时函数内修改了这个值,也不会影响实参。



你可能感兴趣的:(APUE(3rd),学习笔记)