第5章 插叙:进程API 读书笔记

UNIX系统通过一对系统调用fork()exec()来创建新进程,通过wait()等待其创建的子进程执行完成。

5.1 fork()系统调用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    printf("hello world (pid:%d)\n", (int) getpid());
    int rc = fork();
    if (rc < 0) { // fork failed
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
    } else { // parent goes down this path (main)
        printf("hello, I am parent of %d (pid:%d)\n", rc, (int) getpid());
    }
    return 0;
}

对于操作系统来说,看起来有两个一样的程序在运行,并且都从fork()调用中返回。子进程不会从main()函数开始,而是直接从fork()调用中返回,就好像自己调用了fork()

子进程(child)并不是完全拷贝了父进程(parent)。子进程拥有自己的地址空间、寄存器、程序计数器等,但从fork()的返回值不同。父进程获得的返回值是子进程的PID,子进程获得的是0。

输出不是确定的(deterministic)。单CPU的系统上,两者都有先运行的可能。

CPU调度程序(scheduler)决定了某个时刻哪个进程被执行。

5.2 wait()系统调用

父进程等待子进程执行完毕。也可使用waitpid()

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    printf("hello world (pid:%d)\n", (int) getpid());
    int rc = fork();
    if (rc < 0) { // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
    } else { // parent goes down this path (main)
        int rc_wait = wait(NULL);
        printf("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n", rc, rc_wait, (int) getpid());
    }
    return 0;
}

增加了wait()调用后,输出结果变得确定了。即使父进程先运行,会马上调用wait(),停下来等待子进程执行结束后才返回。附注中提到,有些情况下会在子进程退出前返回,细节见man

5.3 最后是exec()系统调用

有多个变体,个人看起来好像只是传参方式不一样而已。让子进程执行和父进程不一样的程序。而fork()只能运行相同程序。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    printf("hello world (pid:%d)\n", (int) getpid());
    int rc = fork();
    if (rc < 0) { // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
        char *myargs[3];
        myargs[0] = strdup("wc"); // program: "wc" (word count)
        myargs[1] = strdup("p3.c"); // argument: file to count
        myargs[2] = NULL; // marks end of array
        execvp(myargs[0], myargs); // runs word count
        printf("this shouldn’t print out");
    } else { // parent goes down this path (main)
        int rc_wait = wait(NULL);
        printf("hello, I am parent of %d (rc_wait:%d) (pid:%d)\n", rc, rc_wait, (int) getpid());
    }
    return 0;
}

exec()会从可执行程序中加载代码和静态数据,覆盖原本的代码段和静态数据,重新初始化堆和栈等内存空间。没有创建新进程,而是直接替换为不同的程序。成功调用不会返回。

5.4 为什么这么设计API

事实证明,分离fork()exec()的做法在构建UNIX shell的时候非常有用,这给了shell在fork之后exec之前运行代码的机会。

做对事(Get it right)。抽象和简化都不能替代做对事。

Lampson – 《Hints for Computer Systems Design》

有许多方式来设计创建进程的API,但fork()exec()的组合既简单又极其强大。

shell找到可执行程序,调用fork()创建新进程,调用exec()的某个变体来执行这个可执行程序,调用wait()等待该命令完成。子进程执行结束后,shell从wait()中返回并再次输出一个提示符,等待用户输入下一条命令。

重定向则在调用exec()前先关闭标准输出(standard output),然后打开文件。其实也可以用dup2()之类的,似乎比较方便。

close(STDOUT_FILENO);
open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);

UNIX管道也是用类似方法实现的,但用的是pipe()系统调用。一个进程的输出被链接到一个内核管道(pipe)上(队列),另一个进程的输入也被链接到了同一个管道上。前一个进程的输出无缝地作为后一个进程的输入。

5.5 其他API

UNIX中还有其他许多和进程交互的方式,比如通过kill()系统调用向进程发送信号(signal),包括要求睡眠、终止等。

第1章 入门 读书笔记

发现这本书真的挺不错的。图灵社区似乎只有纸质书的版权,各大电商均有在售。电子书似乎GitBook上有中文版,如果字体阅读起来难受的话,里面似乎提到,有提供一份正常的。

1.3 Git基础

1.3.5 三种状态

已提交(committed,已存入本地数据库)、已修改(modified,未提交到数据库)和已暂存(staged,对已修改文件做出标识并加入下一次要提交的快照)。

三个主要的区域:Git目录、工作目录、暂存区。

基本工作流:修改工作目录中的文件;暂存,将文件快照加入暂存区;提交,永久保存在Git目录中。

1.5 安装Git

1.5.3 Windows上的安装方法

在Git网站上下载,即Git for Windows项目,独立于Git。

或是安装Windows版的Git,既包含命令行版本的Git,也包括GUI。

1.6 Git的首次配置

使用git config来获取和设置配置变量。

/etc/gitconfig:所有用户及其仓库的值,通过--system参数。
~/.gitconfig~/.config/git/config:针对自己,通过--global参数。
当前仓库的Git目录(.git/config):针对单个仓库。

每一级都会覆盖上一级中的设置。

1.6.1 用户身份

每次提交都需要,并且会被保存,不可更改。

git config --global user.name "John Doe"
git config --global user.email "johndoe@example.com"

也可不带--global参数,不同项目设置不同的用户。

1.6.2 个人编辑器
git config --global core.editor emacs
1.6.3 检查个人设置
git config --list

查看某个键,例如:

git config user.name

1.7 获取帮助

git help <verb>
git <verb> --help
man git-<verb>