大多数操作系统都向应用程序提供了一些核心功能,Linux系统也不例外,通过Linux系统提供的核心功能,应用程序可以很轻松的访问文件,检测用户和组的权限,访问网络资源,以及接收与显示数据等,这些核心功能被称作system calls(系统调用)...

    本文由zengl.com站长对
    http://pan.baidu.com/share/link?shareid=3717576860&uk=940392313 汇编教程英文版相应章节进行翻译得来。

    另外再附加一个英特尔英文手册的共享链接地址:
    http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (在某些例子中会用到)

    本篇翻译对应汇编教程英文原著的第360页到第368页,对应原著第12章 (注意这里的页数不是页脚的页数,而是pdf电子档顶部,分页输入框中的页数,也就是包含了目录,前言部分的总页数,pdf电子档总页数是577,当前已翻译完的页数为368 / 577)。

    大多数操作系统都向应用程序提供了一些核心功能,Linux系统也不例外,通过Linux系统提供的核心功能,应用程序可以很轻松的访问文件,检测用户和组的权限,访问网络资源,以及接收与显示数据等,这些核心功能被称作system calls(系统调用)。

The Linux Kernel Linux系统内核:

    在介绍系统调用之前,有必要先了解下提供这些核心功能的系统内核,Linux操作系统的核心部分就是系统内核,下面会对Linux的系统内核做简明的介绍,并阐述内核提供系统调用的原因。

Parts of the kernel 内核部分:

    内核是系统的核心,它控制着系统里的软硬件,内核主要负责管理以下四个部分:
  • Memory management 内存管理
  • Device management 设备管理
  • File system management 文件系统管理
  • Process management 进程管理
    用户程序可以通过系统调用来访问到内核管理的这些内存,文件和设备等,下面就对内核管理的这几个部件依次进行介绍。

Memory management 内存管理

    内存管理中最重要的一个概念就是"虚拟内存",在虚拟内存里的内存地址被称作线性地址,普通应用程序里访问到的都是虚拟内存里的线性地址,这些线性地址会被系统自动映射为对应的物理内存地址,如下图所示:


图1

    上图位于英特尔手册的第1945页,该图显示的是一种比较常见的线性地址到物理地址的映射方式,这种技术在内核开发里被称作Paging分页技术,这种分页技术让应用程序的开发变得简单,因为这样一来,程序启动时就可以使用相同的线性地址,程序里的函数地址和变量地址等就可以固定不变,方便调试开发,至于具体的物理地址是多少就是内核分配的问题,对用户程序来说,这些变动的不确定的物理地址都是透明的,也无需去关心。

    由于有了虚拟内存,4G的虚拟内存地址都可以映射到128M的物理内存,当实际的物理内存不足时,Linux系统就会将一些不常使用的内存页面保存到磁盘的swap交换分区里,然后将空出来的内存供给需要的程序,当程序需要swap里对应的内存数据时,再从swap交换分区里将数据转到物理内存,从而可以解决物理内存不足的问题。

    以上这些虚拟内存映射和swap交换分区等都是内核处理的事情。

    在Linux系统里,可以通过/proc/meminfo文件里的内容,来查看当前虚拟内存的使用情况,如下所示:

$ cat /proc/meminfo 
MemTotal:         507484 kB
MemFree:            6280 kB
Buffers:           17228 kB
Cached:           292468 kB
SwapCached:           20 kB
Active:           177932 kB
Inactive:         286672 kB
Active(anon):      75844 kB
Inactive(anon):    82132 kB
Active(file):     102088 kB
Inactive(file):   204540 kB
Unevictable:           0 kB
Mlocked:               0 kB
HighTotal:             0 kB
HighFree:              0 kB
LowTotal:         507484 kB
LowFree:            6280 kB
SwapTotal:        522236 kB
SwapFree:         522076 kB
Dirty:                72 kB
Writeback:             0 kB
AnonPages:        154900 kB
Mapped:            60036 kB
Shmem:              3068 kB
Slab:              21504 kB
SReclaimable:      10884 kB
SUnreclaim:        10620 kB
KernelStack:        2360 kB
PageTables:         5248 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      775976 kB
Committed_AS:    1212896 kB
VmallocTotal:     512056 kB
VmallocUsed:       20424 kB
VmallocChunk:     490136 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:       36800 kB
DirectMap2M:      487424 kB
$

    上面输出内容的一部分含义如下:
  • MemTotal: 所有可用RAM大小 (即物理内存减去一些预留位和内核的二进制代码大小)
  • MemFree: LowFree与HighFree的总和,被系统留着未使用的内存
  • Buffers: 用来给文件做缓冲大小
  • Cached: 被高速缓冲存储器(cache memory)用的内存的大小(等于 diskcache minus SwapCache)
  • SwapCached:被高速缓冲存储器(cache memory)用的交换空间的大小
  • Active: 在活跃使用中的缓冲或高速缓冲存储器页面文件的大小,除非非常必要否则不会被移作他用
  • Inactive: 在不经常使用中的缓冲或高速缓冲存储器页面文件的大小,可能被用于其他途径
  • HighFree: 该区域不是直接映射到内核空间。内核必须使用不同的手法使用该段内存
  • SwapTotal: 交换空间的总大小
  • SwapFree: 未被使用的交换空间的大小
  • Dirty: 等待被写回到磁盘的内存大小
  • Writeback: 正在被写回到磁盘的内存大小
  • AnonPages:未映射页的内存大小
  • Mapped: 设备和文件等映射的大小
  • Slab: 内核数据结构缓存的大小,可以减少申请和释放内存带来的消耗
  • SReclaimable:可收回Slab的大小
  • SUnreclaim:不可收回Slab的大小(SUnreclaim+SReclaimable=Slab)
  • PageTables:管理内存分页页面的索引表的大小
  • NFS_Unstable:不稳定页表的大小
    运行在Linux系统里的每个进程都有自己私有的内存空间,一个进程不能访问另一个进程的私有内存空间,普通的用户进程不能访问内核进程所使用的内存空间。

    为了方便进程之间相互共享数据,就有了共享内存,多个进程可以对同一个共享内存区域进行读写数据,内核负责分配和管理这些共享内存区域,可以使用ipcs命令来查看系统里共享内存的使用情况:

$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 0          zengl      600        393216     2          dest         
0x00000000 32769      zengl      600        393216     2          dest         
0x00000000 98307      zengl      600        393216     2          dest         
0x00000000 131076     zengl      600        393216     2          dest         
0x00000000 163845     zengl      600        393216     2          dest         
0x00000000 196614     zengl      600        393216     2          dest         
0x00000000 229383     zengl      600        393216     2          dest         
0x00000000 262152     zengl      600        393216     2          dest         
0x00000000 294921     zengl      600        393216     2          dest         

$

    上面的ipcs命令使用-m参数来查看当前共享内存的情况,每个共享内存区域都有一个创建该共享内存的owner所有者的用户名,perms显示的是当前共享内存对其他用户的许可权限,在权限允许的情况下,其他用户可以通过key值来访问到该段共享内存。

Device management 设备管理:

    内核是通过驱动程序来管理各种设备的,而驱动程序有如下两种方式来添加进内核:
  • 一种是重新编译内核源代码的方式将驱动程序插入内核
  • 一种是以内核模块的方式动态的添加进内核
    第一种方式,每次添加新设备时,都需要重新编译内核源代码,所以这种方式效率比较低,尤其是当Linux内核需要支持较多的硬件设备时。而第二种方式,以内核模块的形式,动态的添加进内核则效率更高些,并且,当不需要某设备时,对应的内核模块还可以动态的从内核里移除。

    Linux之类的Unix系统里,硬件设备都被组织成文件的形式,有以下三种类型的设备文件:
  • Character 字符设备文件
  • Block 块设备文件
  • Network 网络设备文件
    字符设备文件对应终端之类的一次处理一个字符数据的设备,块设备文件对应磁盘之类的一次可以处理一大块数据的设备,网络设备文件对应网卡之类的用于数据收发的设备,使用网络协议在系统里进行内部通信的loopback环回设备也被组织为网络设备文件。

    在linux系统下,可以使用root用户进入dev目录,来查看当前系统所创建的设备文件:

root@zengl:~# cd /dev/
root@zengl:/dev# ls -al sda*
brw-rw---- 1 root disk 8, 0  3月 10 14:03 sda
brw-rw---- 1 root disk 8, 1  3月 10 14:03 sda1
brw-rw---- 1 root disk 8, 2  3月 10 14:03 sda2
brw-rw---- 1 root disk 8, 5  3月 10 14:03 sda5
root@zengl:/dev# ls -al ttyS* | more
crw-rw---- 1 root dialout 4, 64  3月 10 14:03 ttyS0
crw-rw---- 1 root dialout 4, 65  3月 10 14:03 ttyS1
crw-rw---- 1 root dialout 4, 74  3月 10 14:03 ttyS10
crw-rw---- 1 root dialout 4, 75  3月 10 14:03 ttyS11
crw-rw---- 1 root dialout 4, 76  3月 10 14:03 ttyS12
crw-rw---- 1 root dialout 4, 77  3月 10 14:03 ttyS13
crw-rw---- 1 root dialout 4, 78  3月 10 14:03 ttyS14
crw-rw---- 1 root dialout 4, 79  3月 10 14:03 ttyS15
crw-rw---- 1 root dialout 4, 80  3月 10 14:03 ttyS16
crw-rw---- 1 root dialout 4, 81  3月 10 14:03 ttyS17
crw-rw---- 1 root dialout 4, 82  3月 10 14:03 ttyS18
root@zengl:/dev#

    从上面输出可以看到,每个设备都有一个主次设备号,例如sda对应的主设备号为8,次设备号为0,sda1对应的是8, 1 ,相同类型的设备具有相同的主设备号,通过次设备号来识别具体的设备,另外,第一列的第一个字符用于表示该设备文件的类型,例如sda对应的字符为b,表示sda开头的设备文件都是块设备文件(sda之类的本质上就是磁盘设备以及该设备里的各个分区)。ttyS开头的对应的是标准的串口设备,所以它们的第一列第一个字符是c,表示这些属于字符设备文件,它们的主设备号都是4。

    内核通过主次设备号就可以访问到对应的设备。

File system management 文件系统管理:

    不像其他的系统,Linux系统内核可以支持很多不同类型的文件系统,当然必须首先编译内核,让其支持所需的文件系统,下表显示了Linux支持的文件系统类型:

File System 
文件系统
Description 
描述
affs Amiga file system
Amiga文件系统
ext Linux Extended file system
Linux扩展文件系统
ext2 Second extended file system
第二扩展文件系统
ext3 Third extended file system
第三扩展文件系统
hpfs OS/2 high-performance file system
OS/2的高性能文件系统
iso9660 ISO 9660 file system (CD-ROMs)
用于CD光盘的ISO文件系统
minix MINIX file system
MINIX使用的文件系统
msdos Microsoft FAT16
MSDOS使用的FAT16文件系统
ncp Netware file system
Netware文件系统
proc Access to system information
用于访问系统内核信息的
虚拟文件系统
reiserfs Journaling file system
日志文件系统
sysv Older UNIX file system
旧的Unix文件系统
ufs BSD file system
BSD文件系统
umsdos UNIX-like file system that resides on 
top of MS-DOS
类UNIX系统下用于支持MSDOS文件系统
的驱动
vfat Windows 95 file system (fat32)
Windows 95文件系统即FAT32

    Linux内核通过VFS(Virtual File System虚拟文件系统)来访问这些不同类型的文件系统,VFS提供了一种标准的接口,让内核可以和任意类型的文件系统进行通信,在内核与VFS的作用下,用户程序就可以通过系统调用对所支持的任意类型的文件系统进行读写文件的操作。

Process management 进程管理:

    Linux系统里运行中的程序叫做进程,由内核负责管理这些进程,内核创建的第一个进程是init(初始化进程),系统里的其他进程都是由init来启动的。

    大多数Linux系统里都有如下7个run level(运行级别或者称作系统状态):
  • 0 Halt the system 关机状态,不能将系统缺省运行级别设置为0,否则无法启动
  • 1 Single user mode 单用户模式状态,只允许root用户对系统进行维护
  • 2 Basic multi user mode 基本的多用户模式状态,但不能使用NFS(相当于Windows下的网上邻居)
  • 3 Multi user mode 字符文本界面的多用户模式状态
  • 4 未定义
  • 5 Multi user mode with GUI 图形界面的多用户模式状态
  • 6 Reboot the system 重启系统状态,不能将系统缺省运行级别设置为6,否则会一直重启
    多数的桌面的linux系统缺省的runlevel是5,用户登陆时是图形界面,而多数的服务器版本的linux系统缺省的runlevel是3,用户登陆时是字符界面,runlevel 1和2除了调试之外很少使用。

    linux的运行模式比起windows的启动模式的优势在于:你可以在系统空闲时使用init命令切换你现在使用的runlevel,另外,当你关闭或者启动linux系统时你已经在不知不觉中切换了你的runlevel,系统关机进程需要调用runlevel(0或6)来关闭所有正在运行中的进程。

    要查看Linux系统里当前正在运行的进程,可以使用ps命令,ps命令的格式如下:

ps [-aAcdefHjlmNVwy][acefghLnrsSTuvxX][-C <指令名称>][-g <群组名称>]

[-G <群组识别码>][-p <进程识别码>][p <进程识别码>][-s <会话ID>]

[-t <终端机编号>][t <终端机编号>][-u <用户识别码>][-U <用户识别码>]

[U <用户名称>][-<进程识别码>][--cols <每列字符数>]

[--columns <每列字符数>][--cumulative][--deselect][--forest]

[--headers][--help][-- info][--lines <显示行数>][--no-headers]

[--group <群组名称>][-Group <群组识别码>][--pid <进程识别码>]

[--rows <显示列数>][--sid <会话ID>][--tty <终端机编号>]

[--user <用户名称>][--User <用户识别码>][--version]

[--width <每列字符数>]

    ps命令相关参数的说明如下表所示:


Option 选项参数 Description 描述
-a
显示所有终端机下执行的进程,除了session leaders(会话领导者)进程之外。
 
a
显示现行终端机下的所有进程,包括其他用户的进程。
 
-A
显示所有进程。
 
-c
显示CLS和PRI栏位。
 
c
列出进程时,显示每个进程真正的指令名称,而不包含路径,参数或常驻服务的标示。
 
-C <指令名称>
列出该指令的进程的状况。
 
-d
显示所有进程,但不包括session leaders(会话领导者)的进程。
 
-e
此参数的效果和指定"A"参数相同。
 
e
列出进程时,显示每个进程所使用的环境变量。
 
-f
显示UID,PPIP,C与STIME栏位。
 
f
用ASCII字符显示树状结构,表达进程间的相互关系。
 
-g <群组名称>
此参数的效果和指定"-G"参数相同,也能使用session leaders(会话领导者)的名称来指定。
 
g
显示现行终端机下的所有进程,包括群组领导者的进程。
 
-G <群组识别码>
列出属于该群组的进程的状况,也可使用群组名称来指定。
 
h
不显示标题列。
 
-H
显示树状结构,表示进程间的相互关系。
 
-j或j
采用工作控制的格式显示进程状况。
 
-l或l
采用详细的格式来显示进程状况。
 
L
列出栏位的相关信息。
 
-m或m
在进程后显示线程
 
n
以数字来表示USER和WCHAN栏位。
 
-N
显示所有的进程,除了执行ps指令终端机下的进程之外。
 
-p <进程识别码>
指定进程识别码,并列出该进程的状况。
 
p <进程识别码>
此参数的效果和指定"-p"参数相同,只在列表格式方面稍有差异。
 
r
只列出现行终端机正在执行中的进程。
 
-s <会话ID>
列出隶属该会话ID的进程的状况。
 
s
采用进程信号的格式显示进程状况。
 
S
列出进程时,包括已中断的子进程资料。
 
-t <终端机编号>
指定终端机编号,并列出属于该终端机的进程的状况。
 
t <终端机编号>
此参数的效果和指定"-t"参数相同,只在列表格式方面稍有差异。
 
-T
显示现行终端机下的所有进程。
 
-u <用户识别码>
此参数的效果和指定"-U"参数相同。
 
u
以用户为主的格式来显示进程状况。
 
-U <用户识别码>
列出属于该用户的进程的状况,也可使用用户名称来指定。
 
U <用户名称>
列出属于该用户的进程的状况。
 
v
采用虚拟内存的格式显示进程状况。
 
-V或V
显示版本信息。
 
-w或w
采用宽阔的格式来显示进程状况。 
 
x
显示所有进程,不以终端机来区分。
 
X
采用旧式的Linux i386登陆格式显示进程状况。
 
-y
配合参数"-l"使用时,不显示F(flag)栏位,并以RSS栏位取代ADDR栏位
 
-<进程识别码>
此参数的效果和指定"p"参数相同。
 
--cols<每列字符数>
设置每列的最大字符数,设置屏幕宽度。
 
--columns<每列字符数>
此参数的效果和指定"--cols"参数相同。
 
--cumulative
此参数的效果和指定"S"参数相同。
 
--deselect
此参数的效果和指定"-N"参数相同。
 
--forest
此参数的效果和指定"f"参数相同。
 
--headers
重复显示标题列。
 
--help
显示在线帮助。
 
--info
显示排错信息。
 
--lines <显示行数>
设置屏幕高度。
 
--no-headers
此参数的效果和指定"h"参数相同,只在列表格式方面稍有差异。
 
--group <群组名称>
此参数的效果和指定"-G"参数相同。
 
--Group <群组识别码>
此参数的效果和指定"-G"参数相同。
 
--pid <进程识别码>
此参数的效果和指定"-p"参数相同。
 
--rows <显示行数>
此参数的效果和指定"--lines"参数相同。
 
--sid <会话ID>
此参数的效果和指定"-s"参数相同。
 
--tty <终端机编号>
此参数的效果和指定"-t"参数相同。
 
--user <用户名称>
此参数的效果和指定"-U"参数相同。
 
--User <用户识别码>
此参数的效果和指定"-U"参数相同。
 
--version
此参数的效果和指定"-V"参数相同。
 
--width <每列字符数>
此参数的效果和指定"-cols"参数相同。 
 

    下面使用ps命令来查看当前系统里运行的进程情况:

root@zengl:~# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /sbin/init
    2 ?        S      0:00 [kthreadd]
    3 ?        S      0:02 [ksoftirqd/0]
    5 ?        S      0:00 [kworker/u:0]
    6 ?        S      0:00 [migration/0]
    7 ?        S      0:00 [watchdog/0]
    8 ?        S<     0:00 [cpuset]
    9 ?        S<     0:00 [khelper]
   10 ?        S      0:00 [kdevtmpfs]
   11 ?        S<     0:00 [netns]
   12 ?        S      0:00 [sync_supers]
   13 ?        S      0:00 [bdi-default]
   14 ?        S<     0:00 [kintegrityd]
   15 ?        S<     0:00 [kblockd]
   16 ?        S      0:01 [irq/9-acpi]
   17 ?        S<     0:00 [ata_sff]
   18 ?        S      0:00 [khubd]
   19 ?        S<     0:00 [md]
   22 ?        S      0:00 [khungtaskd]
   23 ?        S      0:00 [kswapd0]
   24 ?        SN     0:00 [ksmd]
   25 ?        S      0:00 [fsnotify_mark]
   26 ?        S      0:00 [ecryptfs-kthrea]
   27 ?        S<     0:00 [crypto]
   35 ?        S<     0:00 [kthrotld]
   37 ?        S      0:01 [irq/5-ahci]
   38 ?        S      0:00 [scsi_eh_0]
   39 ?        S      0:00 [irq/14-ata_piix]
.................................
root@zengl:~# 

    上面ps使用ax参数来显示出当前系统里所有运行的进程信息,第一个PID列用于显示进程的标识符,每个进程的标识符都是唯一的,例如init进程的PID为1,表示它是内核创建的第一个进程,其他进程的PID都比init进程的大,第二列显示的是进程相关的终端,第三列显示的是进程当前的状态,Linux系统里的进程有如下几种状态:
  • D  Uninterruptible sleep (usually IO)
    不可中断的深度睡眠,一般由IO引起,同步IO在做读或写操作时,此进程不能做其它事情,只能等待,这时进程处于这种状态,如果程序采用异步IO,这种状态应该就很少见到了
  • R  Running or runnable (on run queue) 
    进程处于运行或就绪状态
  • S  Interruptible sleep (waiting for an event to complete)
    可接收信号的睡眠状态,sleep函数可演示这种状态
  • T  Stopped, either by a job control signal or because it is being traced.
    被ctrl+z中断或被trace
  • W  paging 无驻留页 (not valid since the 2.6.xx kernel 从2.6内核开始该状态无效)
  • X  dead (should never be seen)
    进程已经完全死掉,不可能看见这种状态的
  • Z  Defunct ("zombie") process, terminated but not reaped by its parent.
    进程已经终止,但是其父进程没有来得及处理它,多进程写不好的话,这种状态是常见的
  • <  High-priority process
    (高优先级进程)
  • N  低优先级进程
  • L  内存锁页
  • s  包含子进程
    在上面ps ax命令的输出里可以看到最后一列有些命令被方括号括起来了,是因为这些命令属于系统进程或内核线程,无法定位到他们的参数信息,所以就将命令名称显示在方括号里了。

Linux kernel version Linux内核版本:

    Linux内核开发采用严格的版本控制系统,Linux内核版本的格式如下:

linux-a.b.c

    其中a是主版本号,b是次版本号,c是修正版本号。

    在命令行下可以使用uname命令来查看Linux的版本信息:

root@zengl:~# uname -a
Linux zengl 3.2.0-52-lowlatency-pae #54-Ubuntu SMP PREEMPT Tue Aug 6 02:02:19 UTC 2013 i686 athlon i386 GNU/Linux
root@zengl:~#

    上面显示出我的系统所使用的Linux内核版本号为3.2.0 。

    以上介绍的是Linux内核相关的内容,下一篇介绍系统调用部分。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~
上下篇

下一篇: 汇编里使用Linux系统调用 (二)

上一篇: 汇编函数的定义和使用 (三) 汇编函数结束篇

相关文章

使用IA-32平台提供的高级功能 (一)

汇编里使用Linux系统调用 (三) 系统调用结束篇

汇编数据处理 (四) 数据处理结束篇

汇编中使用文件 (三) 使用文件结束篇

汇编函数的定义和使用 (一)

基本数学运算 (四) 基本运算结束篇