系统调用名 | 含义 |
---|---|
int fork() |
创建一个进程,返回子进程的PID。 |
int exit(int status) |
终止当前进程;报告状态给执行wait()系统调用的父进程。没有返回。 |
int wait(int *status) |
等待子进程退出;退出状态为 *status ;返回子进程的PID。 |
int kill (int pid) |
终止进程号为PID的进程。返回0表示成功,或-1表示错误。 |
int getpid() |
返回当前进程的PID。 |
系统调用名 | 含义 |
---|---|
int sleep(int n) |
暂停n个时钟周期。 |
int exec(char *file,char *argv[]) |
用参数加载文件并执行;仅当出错时返回。 |
char *sbrk(int n) |
将进程内存增加n个字节。返回新内存的开始地址。 |
int open(char *file,int flags) |
打开文件;标志flag表示文件操作的读/写属性;返回一个fd(文件描述符)。 |
int write(int fd,char *buf,int n) |
从buf向文件描述符fd写入n个字节;返回n。 |
系统调用名 | 含义 |
---|---|
int read(int fd,char *buf,int n) |
将n个字节读入buf;返回读取的数字;如果文件结束,则为0。 |
int close(int fd) |
释放打开的描述符为fd的文件。 |
int dup(int fd) |
返回一个新的文件描述符,引用与文件描述符相同的文件。 |
int pipe(int p[]) |
创建一个管道,将读/写文件描述符放在p[0]和p[1]中。 |
int chdir(char *dir) |
更改当前目录。 |
系统调用名 | 含义 |
---|---|
int mkdir(char *dir) |
创建一个新目录。 |
int mknod(char *file, int, int) |
创建一个设备文件。 |
int fstat(int fd, struct stat *st) |
将文件fd的元信息放入 *st |
int stat(char *file, struct stat *st) |
将文件 *file 的元信息放入 *st |
int link(char *file1,char *file2) |
为文件file1创建另一个名称(file2)。 |
int unlink(char *file) |
删除文件。 |
fork.c exec.c forkexec.c ...
list.c open.c echo.c copy.c ...
pipe1.c pipe2.c redirect.c ...
例如:copy.c,将输入复制到输出
从输入中读取字节,将其写入输出中
$ copy
copy.c是用C语言编写的
read()和write()是系统调用
read()/write()第一个参数是"文件描述符"(fd)
传递给内核,告诉它要读/写哪个 "打开的文件"。
必须先前已经打开过的一个FD(描述符)连接到一个文件/设备/socket
一个进程可以打开许多文件,有许多描述符
UNIX惯例:FD: 0是 "标准输入",1是 "标准输出"
read()第二个参数是一个指针,指向要读入的一些内存。
read()第三个参数是要读取的最大字节数
注:read()可以少读,但不能多读
返回值:实际读取的字节数,或者-1表示错误
注意:copy.c并不关心数据的格式。
UNIX的I/O是8位字节
解释是特定于应用的,例如数据库记录、C源码等
文件描述符从何而来?
例如:open.c,创建一个文件
$ open
$ cat output.txt
open() 创建一个文件,返回一个文件描述符(或-1表示错误)。
FD是一个小整数,FD索引到一个由内核维护的每进程表中
不同的进程有不同的FD命名空间。例如,FD 1对不同的进程意味不同
进一步细节可以参考UNIX手册,例如 "man 2 open"。
man 1是shell命令如ls;man 2是系统调用如open;man 3是函数说明
当程序调用open()这样的系统调用时会发生什么?
当程序调用open()这样的系统调用时会发生什么?
在向UNIX的命令行界面(shell)输入信息。
shell打印出"$"的提示。
shell让你运行UNIX的命令行工具
对系统管理、处理文件、开发和编写脚本很有用
$ ls
$ ls > out
$ grep x < out
但通过shell来支持分时共享多任务执行是UNIX设计之初的重点。
可以通过shell行使许多系统调用。
shell为你输入的每个命令创建一个新的进程,例如,对于
$ echo hello
fork()系统调用创建一个新的进程
$ fork
内核创建一个调用进程的副本
唯一的区别:fork()在父进程中返回一个pid,在子进程中返回0。
pid(进程ID)是一个整数,内核给每个进程一个不同的pid
因此,fork.c的 "fork()返回 "在两个进程中都会执行
"if(pid == 0) "实现对父子进程的区分
我们怎样才能在这个进程中运行一个新程序呢?
例如:exec.c,用一个可执行文件代替调用进程。
shell是如何运行一个程序的,例如
$ echo a b c
一个程序被储存在一个文件中:指令和初始内存,由编译器和链接器创建
所以有一个叫echo的文件,包含对 exec
系统调用的操作命令
exec()用一个可执行文件取代当前进程
exec(filename, argument-array)
argument-array保存命令行参数;exec传递给main()
cat user/echo.c
echo.c显示了一个程序如何看待它的命令行参数
例如:forkexec.c,fork()一个新进程,exec()一个程序。
$ forkexec
forkexec.c包含了一个常见的UNIX习惯用语。
shell对你输入的每个命令都进行fork/exec/wait操作。
在wait()之后,shell会打印出下一个提示信息
在后台运行 -- &
-- , shell会跳过wait()
exit(status) --> wait(&status)
status约定:0 = 成功,1 = 命令遇到了一个错误
注意:fork()会复制,但exec()会丢弃复制的内存。
这可能看起来很浪费
你可以通过 "写时复制 "技术透明地消除复制
例子:redirect.c,重定向一个命令的输出
shell对此做了什么?
$ echo hello > out
答案:fork,改变子进程的FD1,执行echo
$ redirect
$ cat output.txt
注意:open()总是选择最低的未使用的FD;选择1是由于close(1)。
fork、FD和exec很好地互动,以实现I/O重定向
独立的fork-then-exec给子进程一个机会在exec之前改变FD。
FDs提供了指示作用
命令只需使用FDs 0和1,不需要知道它们的位置
exec保留了sh设置的FDs
因此:只有sh需要知道I/O重定向,而不是每个程序
一些值得思考的问题:
UNIX的设计很好用,但我们会看到其他的设计
例子:pipe1.c,通过一个管道进行通信
shell是如何实现的
$ ls | grep x
$ pipe1
一个FD可以指一个 "管道",也可以指一个文件。
pipe()系统调用创建了两个FD
内核为每个管道维护一个缓冲区
例子:pipe2.c,在进程间通信。
管道与fork()结合得很好,可以实现ls | grep x。
shell创建一个管道。
然后分叉(两次)。
然后将ls的FD1连接到管道的写FD。
和grep的FD 0连接到管道上。
$ pipe2 -- 一个简化版本
管道是一个独立的抽象概念,但与 fork() 结合得很好
小结