1.4 系统调用
请记住以下事实:
任何单CPU计算机一次只能执行一条指令。
如果一个进程正在用户态下运行一个程序,然后它需要一个系统服务,如读取文件数据,那么它就必须执行一个陷阱或系统调用指令,把控制权交给操作系统。操作系统通过检查此次调用的参数,判断出该进程所需要的服务类型,然后去执行相应的服务功能并把控制权交还给用户进程,从系统调用后面的那条指令开始执行。
从某种意义上来说,发出一个系统调用类似于发出一个特殊的函数调用,两者的区别仅在于,发出系统调用后,将进入内核或其他的特权操作系统组件,而函数调用则不会这样。
MINIX3总共有53条系统调用,被分为6大类:
进程管理、信号、文件管理、目录及文件系统管理、保护、时间管理。
1.4.1 进程管理的系统调用
在MINIX3中,fork是创建一个新进程的唯一途径。它实际上创建的是原进程的一个副本,包括文件描述符、寄存器的值等,所有内容都是完全相同的。
调用fork后,原进程与新进程各自执行互不相关。一个进程的变量值发生变化,并不会影响到另一个进程(代码段不可修改,父子进程共享)。
正常情况下,如果fork函数的返回值为0,表示当前进程是子进程;如果返回值为一个正整数,则表明当前进程是父进程,而该整数即为子进程的标识号PID。因此,在调用fork后,父子进程内容完全相同,但是通过函数返回值可以将父子进程区分开来。
多数情况下,在执行完fork之后,子进程需要执行一段与父进程不同的代码。为了等待子进程结束,父进程会执行一个waitpid系统调用,该调用将使父进程阻塞,直到子进程结束。
#define TRUE 1
while(TRUE)//无限循环
{
tpye_prompt();//在屏幕上显示提示符
read_command(command, parameters);//从终端读取输入
if(fork()!=0)//创建子进程
{
/*Parent code.*/
waitpid(-1, &status, 0);//等待子进程退出
}
else
{
/*Child code.*/
execve(command, parameters, 0);//执行命令
}
}
一般情况下,execve函数有三个参数:待执行的文件名、指向参数数组的指针和指向环境数组的指针。
在MINIX3中,进程的内存空间被分为三个部分:代码段(即程序代码)、数据段(即变量)、栈段。数据段从下往上增长,而栈从上往下增长。在这两者之间的是空闲的地址空间。栈的增长是随着程序的执行自动进行的,而数据段的扩展则需要通过brk系统调用来显示完成。
下一个系统调用是getpid,返回调用进程的进程标志号PID。在调用fork时,只有父进程能够获得子进程的PID。如果子进程想要知道它自己的PID,就必须使用getpid。
最后一个系统调用是ptrace,它只要用来对被调试的程序进行控制。通过ptrace,调试器可以读、写被控进程的地址空间,并进行其他方式的管理。