ALP Chapter 8 Linux系统函数(Linux System Calls)
-
到目前为止我们介绍的所有API都分可归为两类:
- 库函数(library function):普通的函数,由一个不属于我们程序的外部的库编写,这种外部的库的一个典型例子是C库。这种函数的参数传递是通过寄存器或者堆栈来实现的,执行的时候直接跳转到该函数代码的开头,也就是我们学过的典型的C语言函数调用的方式。这种库函数的例子:getopt_long, mkstemp
- 系统函数(system call):由Linux内核实现的函数。这种函数的参数传递和调用都是由内核实现的,并且需要做一个额外的操作。(例如在操作系统上提到的,转换进程的状态,由用户态转为内核态)可是为什么我们在实际写代码的时候没有任何的区别?因为GNU C库已经为我们给这些系统函数外面包了一层,我们调用起来就和调用库函数是一样的。这种系统函数的例子:open, read
- See it before explore it,我们来看看系统函数是什么样子,先睹为快:/usr/include/asm/unistd.h
8.1 使用strace (Using strace)
- Strace命令可以监视另一个程序的执行,并列出该程序所调用的所有system calls和所接收到的所有signal。
- strace输出的每一行代表一个system call或者signal。对于system call来说,首先是该system call的名字,然后是其参数,最后是返回值。而signal的输出则是signal symbol和一个signal字符串的形式。
- 接下来我们进入主题,介绍几个非常重要的系统函数:
8.2 access: 测试文件访问权限
- 系统函数access测试当前进程是否有权限访问一个文件。
- access的第一个参数是文件的路径,第二个参数的值可以是:R_OK, W_OK, X_OK,分部对应读,写和执行。另一个可选的值是F_OK,这种情况下access只检测该文件是否存在。
- access如果返回0则表示当前进程拥有指定的权限。返回-1则表示没有权限,并且errno被设为EACCES(或者EROFS,如果我们对一个只读文件要求可写权限的话)
8.3 fcntl: 加锁以及其他一些文件操作 (Locks and Other File Operatioins)
- 系统函数fcntl的第一个参数是文件描述符,第二个是要对文件执行的操作。对某些操作来说,还有额外的参数。
- 对于加锁操作来说,有两种锁,读锁和写锁。很显然,读锁是可以并存的,即多个进程可以在同一个文件上加上各自的读锁,写锁是排他的,同一时间只有一个进程可以在一个文件上加写锁。
- 注意:一个文件被加锁并不意味着他不能被别的进程打开,读或者写。只有当别的进程试图对该文件加锁时,现有的锁才会发生作用。
- 其实在加锁/解锁方面,另一个函数flock有着相同的效果。我们之所以选择使用fcntl的原因是:它支持NFS文件系统!
8.4 fsync和fdatasync:flush磁盘缓冲区 (fysnc and fdatasync: Flushing Disk Buffers)
- fsync的参数只有一个:需要flush的文件描述符。fdatasync的作用也是一样。
- 他们的区别:fsync保证会更新文件的修改时间,而fdatasync不保证。所以理论上fdatasync是会比fsync更快一点的
- 但是……目前的linux版本里面这两个是完全一样的,所以大家随便用吧。
8.5 getrlimit和setrlimit:资源限制 (getrlimit and setrlimit: Resource Limits)
- 这两个系统函数是和resource limit相关的。你用过ulimit命令吗?(反正我没用过)
- 对于每个资源来说有两个limit:一个是hard limit,一个是soft limit,其中后者永远不能超过前者,并且只有拥有superuser权限的进程可以更改前者。
- getrlimit和setrlimit的参数相同:第一个参数是资源的类型,第二个参数是rlimit结构的指针,这个结构里面就只有两个成员:hard limit和soft limit
-
几个重要的资源类型
- RLIMIT_CPU:程序执行的最大CPU时间,单位是秒。超过之后程序中止,中止的信号是SIGXCPU
- RLIMIT_DATA:程序执行的最大内存
- RLIMIT_NPROC:程序孵出的最大子进程数量
- RLIMIT_NOFILE:程序打开的文件的最大数量
8.6 getrusage:进程统计信息 (getrusage: Process Statistics)
-
getrusage有两个参数
- 第一个参数是类型,如果是RUSAGE_SELF,则返回其自身的统计信息;如果是RUSAGE_CHILDREN,则返回其属下的所有已结束的子进程的统计信息。
- 第二参数是rusage结构的指针。这个结构里面几个比较重要的成员是:
- ru_utime,类型是timeval结构,记录user time;
- ru_stime,类型是timeval结构,记录system time;
- ru_maxrss,记录最大内存使用量
8.7 gettimeofday: Wall-Clock时间(gettimeofday: Wall-Clock Time)
- gettimeofday返回的是系统的wall-clock时间,这个wall-clock时间我还真的不知道怎么翻译,它是一个timeval结构,以秒为单位,里面只有两个域,第一个域就是秒的整数部分,第二个域是毫秒。这个秒的计算方法是:从1970年1月1号的凌晨到现在。
- 很显然,这么长长的秒非常难用。所以localtime这个函数负责把timeval结构转化为tm结构,tm结构就很简单了,有tm_year,tm_mon,tm_day,tm_hour等等域,一看就知道什么意思,不说了。
- 有了tm结构之后,可以用strftime来获得一个很好看的输出。这个函数和printf差不多,不同的只是它里面的字符串%Y表示年,%m表示月,等等,具体去看man page吧。(不是我偷懒,要学linux就必须有看man page的习惯,我现在是越来越深刻的认识到这一点了)
8.8 mlock家族:锁定物理内存(The mlock Family: Locking Physical Memory)
- 什么叫锁定物理内存?我们先来复习一下操作系统里面换页的概念:页是内存分配的基础,一个程序运行的时候占据了物理内存中的若干页。当操作系统发现物理内存不够用的时候,他会根据一个调度算法(通常是找出最近最少使用的)找出最应该换出的页,把那个页空闲出来分配给需要的进程。所以这里锁定物理内存的意思就是:我死活就霸着我指定的这些页不放,你操作系统要找空闲页找别的进程去,即时我很久没有用他们,你也不准把他们释放出来给别人用!颇有点站着茅坑不拉屎的气魄。
- 这样做当然是有缺点的,我们可以想象一下每个进程都这么来一下我们的操作系统同学会有多么的郁闷与无奈。但这个方法确实也是相当有用的:1,对于时间要求很高的程序,换页耗时;2,对于安全性很高的程序,换页势必要把页内的东西写出到某个swap文件,这个文件被侵入者偷看了怎么办?
- 锁定就是mlock,解锁就是munlock,锁定当前进程申请的(也可以包括未来申请的)所有内存空间就是mlockall,释放所有内存空间就是munlockall(这个也可以用来释放mlock所锁住的空间)
- 锁住很大的空间可能会导致你的操作系统忙死,导致频繁的换页操作(这个就是著名的thrashing现象了)。所以理所当然的,只有superuser才可以调用mlock和malockall。
-
另外注意一个很猥琐的现象:如果你的操作系统不幸是采用copy-on-write战术的,你申请一块内存然后马上mlock它就很可能导致不是你想要的结果。比较猥琐的一个对付方法是:申请一块内存时候,给这个内存的每个页都写上那么一点东西,一个bit足矣。例如下面:
for (i = 0; i < alloc_size; i += page_size) memory[i] = 0;
8.9 mprotect: 设置内存访问权限(mprotect: Setting Memory Permissions)
- 还记得mmap函数吧?把一个文件做内存映射,映射的时候可以指定访问权限。mprotect可以修改这样的内存的访问权限。
- 违反mprotect的设定访问而访问内存会生成SIGSEGV signal,所以我们可以通过catch这个signal来监控这块内存的被访问情况
- 上文只提到了mmap之后的内存可以用mprotect来管理,那么一般的内存呢?例如malloc申请出来的,也可以这样吗?根据本人的试验,答案是:不可以。
8.10 nanosleep: 高精度休眠(nanosleep: High-Precision Sleeping)
- 不说了,一听名字就够拉风了,也知道它是干什么的。注意参数是timespec结构。但是!它远远没有它的名字那么厉害。(我也想要它有纳秒级别的响应啊,但拜托你先看看你cpu的主频)通常来说,它的最小精度是10毫秒。
- 另外一点,sleep会在收到中断的时候被“打醒”,nanosleep也会,但它的第二个参数可以返回还有多少时间才会到它正常的苏醒。
8.11 readlink: 读取symbolic links(readline: Reading Symbolic Links)
- readlink:三个参数,第一个是symbolic link的路径,第二个是存放link目标的buffer,第三个是buffer的长度。
- 正常情况下,readlink会返回target字符串的长度。如果第一个参数不是symbolic link,返回-1。
- 注意:readlink返回的target字符串不是以’\0’结尾的,需要我们自己添上。
8.12 sendfile:快速数据传输(sendfile: Fast Data Transfers)
- 以前我们复制文件的时候怎么做?打开源文件,打开目标文件,申请一块内存,从源文件读,把该内存写满,再把内存的东西写到目标文件。如此反复,直到所有源文件的内容都拷贝到目标文件为止。
- 现在不需要这种低效的方法了!也不需要额外的这块内存,用sendfile你可以方便快捷的完成这种操作!(怎么感觉像促销广告?!)
- 需要注意的一点:sendfile参数中的文件描述符可以是磁盘文件,socket文件,或者其他设备。(不限于磁盘文件)
8.13 setitimer: 设置时间间隔(setitimer: Setting Interval Timers)
- 看标题大概不明白这个是干什么的。那么,alarm知道吗?setitimer就是alarm的强化版。
-
简单的说setitimer会设置时间间隔,在经过指定的时间后,会发出一个signal。setitimer和alarm不同的是,它有三种类型(由第一个参数指定):
- ITIMER_REAL类型:当经过指定的时间后,发出SIGALRM的signal(和alarm相同)
- ITIMER_VIRTUAL类型:当程序执行过指定的时间后(内核或者其他进程执行的时间不计算在内),发出SIGVTALRM的signal
- ITIMER_PROF类型:当程序已经被该程序所导致的内核代码经过指定的时间后,发出SIGPROF的signal。
8.14 sysinfo: 获取系统统计信息(sysinfo: Obtaining System Statistics)
- 很简单,直接调用这个函数。参数类型是struct sysinfo*。这个结构的详细信息?看man page去
8.15 uname
-
这个也是拿来获取系统信息的。不过是获取诸如网络名,域名,操作系统版本等等。参数类型是struct utsname*,还是那句话,查 man page
转自 Colar的共享空间
其他文章:
google_ad_client = "pub-2416224910262877"; google_ad_width = 728; google_ad_height = 90; google_ad_format = "728x90_as"; google_ad_channel = ""; google_color_border = "E1771E"; google_color_bg = "FFFFFF"; google_color_link = "0000FF"; google_color_text = "000000"; google_color_url = "008000";