程序是存放在磁盘上、处于某个目录中的可执行文件。程序的执行实例称为进程。UNIX系统确保每一个进程都有唯一的数字标识符,称为进程ID。进程ID总是一非负数。
《UNIX环境高级编程》P8: 程序清单1-4 打印进程ID(有改动)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { printf("hello world from process ID %d\n", getpid()); // getpid()获取进程ID exit(0); }
用户进程控制的函数主要有三个:fork、exec和waitpid。(exec函数有六种变体,但经常把它们统称为exec函数)
《UNIX环境高级编程》P9: 程序清单1-5 从标准输入读命令并执行(有改动)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #define MAXLINE 4096 int main() { char buf[MAXLINE]; pid_t pid; int status; printf("%% "); while (fgets(buf, MAXLINE, stdin) != NULL) { // 从标准输入一次读取一行,当键入文件结束符(Ctrl+D),fgets返回null指针 if (buf[strlen(buf) - 1] == '\n') // fgets返回的每一行都以换行符终止,随后一个null字节,需要去除换行符 buf[strlen(buf) - 1] = 0; // 用一个null字节替换换行符 if ((pid = fork()) < 0) { // fork创建一个新进程(子进程)。新进程是调用进程的复制品 fprintf(stderr, "fork error"); } else if (pid == 0) { // fork对父进程返回子进程ID(非负),对子进程返回0。(fork调用一次,返回两次) /* 子进程 */ execlp(buf, buf, (char *) 0); // 执行从标准输入读入的命令,新的程序文件替换子进程原先的执行文件 fprintf(stderr, "couldn't execute: %s\n", buf); exit(127); } /* 父进程部分,子进程不会执行 */ if (( pid = waitpid(pid, &status, 0)) < 0) // 父进程等待子进程终止,pid为子进程ID,status返回子进程的状态 fprintf(stderr, "waitpid error"); printf("%% "); } printf("\n"); exit(0); }
执行:
$ ./05 % dir 01 01.c 02 02.c 03 03.c 04 04.c 05 05.c Makefile %
多个控制线程能充分利用多处理器的并行性。
在一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。因为它们能访问同一存储区,所以各个线程在访问共享数据时需要采取同步措施以避免不一致性 。
与进程相同,线程也用ID标识。但线程ID只在它所属进程内起作用。当在同一进程中对多个线程进行操作时,用线程ID引用相应的线程。
我对fork函数的理解:
1. 执行fork创建一个新进程,新进程是调用进程的复制品。执行到的位置都一样。
2. fork向父进程返回子进程ID,对子进程返回0,所以父、子进程会执行不同部分。(父、子进程包含相同的代码)
子进程执行部分:
} else if (pid == 0) { // fork对父进程返回子进程ID(非负),对子进程返回0。(fork调用一次,返回两次) /* 子进程 */ execlp(buf, buf, (char *) 0); // 执行从标准输入读入的命令,新的程序文件替换子进程原先的执行文件 fprintf(stderr, "couldn't execute: %s\n", buf); exit(127); }
父进程执行部分:
/* 父进程部分,子进程不会执行 */ if (( pid = waitpid(pid, &status, 0)) < 0) // 父进程等待子进程终止,pid为子进程ID,status返回子进程的状态 fprintf(stderr, "waitpid error"); printf("%% ");
3. 当子进程执行到 execlp函数时,新的程序文件会替换原来的程序文件。因此子进程可以执行不同的程序。