文章目录
系统调用
系统调用的好处:
- 释放了程序员生产力
- 提高系统稳定性
- 使得多任务以及虚拟内存成为可能
前置知识
操作系统主要有两项功能
- 向用户程序提供一个友好的编程接口,即
系统调用
- 管理计算机资源(包括CPU、内存、磁盘、网卡等外设,以及进程管理,线程管理,文件管理等)
虽然系统调用在程序员眼里仅仅是一个普通的函数调用,但是深刻理解系统调用对于理解操作系统的运行方式来说是非常重要的。简而言之,要想成为编程高手,你需要理解系统调用。
API和系统调用
API和系统调用是多对一的关系,如下:
POSIX
由于历史原因,现存有很多基于Unix的操作系统,但是又包含了自己的实现。因此为了方便在这些系统是进行软件开发,提出了POSIX标准,POSIX标准主要是用来统一各个Unix平台上的API而不是这些平台上的系统调用,这些Unix系统可以有不同的系统调用,但是对外的API要提供一个大家都认可的统一的格式,POSIX就是来规定这些格式的。
系统调用
系统调用设计思想
系统调用带来的好处
-
释放了程序员生产力
系统调用对程序员屏蔽了操作系统对计算机资源管理的细节,因此程序员在进行比如文件读写这样的操作时根本无需关心这些文件放在了磁盘中的什么位置上,这些文件是通过什么样的文件系统来管理的等等事情。 -
提高系统稳定性
作为用户程序和操作系统之间的一个屏障
,系统调用保护了操作系统不受用户程序的干扰。通过系统调用,操作系统可以对用户请求进行权限以及合法性检查,这就阻止了用户程序随意使用系统资源
。
同时作为用户程序向操作系统发起请求的唯一合法途径
,系统调用起到了类似海关的作用,这些无疑提高了系统稳定性。
系统调用和库函数的区别
系统调用执行过程
注意:
系统调用的分类(按功能分类)
由于一个系统的功能都是通过系统调用对外提供的,因此应用程序能够实现的最强大的功能不会超过系统调用提供给应用程序的能力。
根据类型系统调用大体可以划分为以下几类:
-
进程控制:fork和wait
一个运行中的进程可以创建另外一个进程去完成某项工作,这样当前进程就有机会去处理自己感兴趣的事情,这类系统调用在Linux中是fork()
,当我们创建新的进程后,可能需要等待其运行完成,这时我们需要的系统调用是Linux下的wait()
。 -
文件管理:create open read write
我们通常需要创建文件create()
来持久化的保存信息,文件创建完毕后,通常在使用前我们要首先打开文件open()
,然后才能进行读写read()、write()
。文件使用完毕后,通常需要关闭文件close()
。关于文件的这些操作都是通过系统调用来完成的。 -
设备管理:request release
我们的程序在运行过程中需要使用很多系统资源才能完成任务,比如请求分配内存、访问磁盘、通过网卡收发网络数据等等,如果这些资源当前是可用的,那么我们的程序在得到这些资源后可以继续运行,否则只能暂停运行我们的程序直到这些资源可用为止。因此用户程序通常需要首先请求资源request
(),使用完毕后释放资源release
()。由于在Unix/Linux系统中将这些硬件资源抽象成了文件(file)
,因此我们可以通过对文件的读写read()、write()
就能实现操作设备的目的。 -
信息维护:date time dump trace
通常情况下我们需要向操作系统请求一些只有操作系统才知道的信息
,比如当前的日期date
(),时间time
(),或者操作系统的版本信息,当前可用内存大小,剩余磁盘大小等等。
另一种比较有用的信息是程序在内存中的运行数据
,通过dump
()进程在内存中的数据以及进程中函数的调用信息trace
(),我们就可以利用调试器(比如Linux下的gdb)来调试有问题的程序。 -
通信:
我们的进程可能需要与其它进程通信才能完成某项功能,在这里通常有两种通信模型。- 一种是
消息传递类
,比如常用的网络通信,在网络通信中我们通过读写socket来实现网络数据的接收recv
()和发送send
(),本地的进程之间通信会通过比如Linux下的pipe
()这类系统调用来完成。 - 另一种是
共享内存
。即进程间通信模型。
- 一种是
下面,我们会列出一些常用的 POSIX
系统调用,POSIX 系统调用大概有 100 多个,它们之中最重要的一些调用见下表
进程管理
调用 | 说明 |
---|---|
pid = fork() | 创建与父进程相同的子进程 |
pid = waitpid(pid, &statloc,options) | 等待一个子进程终止 |
s = execve(name,argv,environp) | 替换一个进程的核心映像 |
exit(status) | 终止进程执行并返回状态 |
文件管理
调用 | 说明 |
---|---|
fd = open(file, how,…) | 打开一个文件使用读、写 |
s = close(fd) | 关闭一个打开的文件 |
n = read(fd,buffer,nbytes) | 把数据从一个文件读到缓冲区中 |
n = write(fd,buffer,nbytes) | 把数据从缓冲区写到一个文件中 |
position = iseek(fd,offset,whence) | 移动文件指针 |
s = stat(name,&buf) | 取得文件状态信息 |
目录和文件系统管理
调用 | 说明 |
---|---|
s = mkdir(nname,mode) | 创建一个新目录 |
s = rmdir(name) | 删去一个空目录 |
s = link(name1,name2) | 创建一个新目录项 name2,并指向 name1 |
s = unlink(name) | 删去一个目录项 |
s = mount(special,name,flag) | 安装一个文件系统 |
s = umount(special) | 卸载一个文件系统 |
其他
调用 | 说明 |
---|---|
s = chdir(dirname) | 改变工作目录 |
s = chmod(name,mode) | 修改一个文件的保护位 |
s = kill(pid, signal) | 发送信号给进程 |
seconds = time(&seconds) | 获取从 1970 年1月1日至今的时间 |
上面的系统调用参数中有一些公共部分,例如 pid 系统进程 id,fd 是文件描述符,n 是字节数,position 是在文件中的偏移量、seconds 是流逝时间。
汇总版:
UNIX | Win32 | 说明 |
---|---|---|
fork | CreateProcess | 创建一个新进程 |
waitpid | WaitForSingleObject | 等待一个进程退出 |
execve | none | CraeteProcess = fork + servvice |
exit | ExitProcess | 终止执行 |
open | CreateFile | 创建一个文件或打开一个已有的文件 |
close | CloseHandle | 关闭文件 |
read | ReadFile | 从单个文件中读取数据 |
write | WriteFile | 向单个文件写数据 |
lseek | SetFilePointer | 移动文件指针 |
stat | GetFileAttributesEx | 获得不同的文件属性 |
mkdir | CreateDirectory | 创建一个新的目录 |
rmdir | RemoveDirectory | 移除一个空的目录 |
link | none | Win32 不支持 link |
unlink | DeleteFile | 销毁一个已有的文件 |
mount | none | Win32 不支持 mount |
umount | none | Win32 不支持 mount,所以也不支持mount |
chdir | SetCurrentDirectory | 切换当前工作目录 |
chmod | none | Win32 不支持安全 |
kill | none | Win32 不支持信号 |
time | GetLocalTime | 获取当前时间 |
fork详解
是什么?
fork函数通过系统调用创建一个与原进程几乎完全相同
的进程,两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
占用内存
一个进程调用fork()函数后,系统先给新的进程分配资源
,例如存储数据和代码的空间。
然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
创建新进程成功后,系统中出现两个基本完全相同的进程。
所以如果一个进程2GB, fork 之后这两个进程一共占用4G内存。