<<unix/linux programming>>学习标记整理

2016-12-02 12:52:23来源:网络收集作者:admin人点击

SI/O of linux/unix
------------------------------------------------------
1、linux/unix 中文件名的最大长度是 NAME_MAX,路径的最大长度是 PATH_MAX
2、用于打开和关闭一个文件流的函数有三个
(1)、fopen
(2)、fclose()
(3)、freopen()
3、我们可以使用 fputs(stream,buffer)向文件写入内容,
可以使用 fgets(buffer,sizeof(buffer),stream) read string from stream.
4、无格式 IO
(1)、字符 I/O:所谓字符 I/O,就是每次只读出 1 个字符
函数接口:fgetc(stream) and getc(stream) getchar(void)
fputc(int c,stream) and putc(int c,stream),putchar(int
c)
fgetc(stream):get the character from stream one by one,return int
getc(stream):just like fgetc(stream)
getchar(void):just like getc(stdin)
fputc(int c,stream):trans (int)c to (u_char)c,then write to stream
putc(int c,stream):like fputc(int c,stream)
putchar(int c):same ...
(2)、行 I/O:所谓行 I/O,就是每次处理的是一行
函数接口:
fgets(char* s,int count,FILE* fp):read count characters
gets(char*s):just read one line end with '/n'
getline(char** ptr,size_t *n,FILE* fp):read line
getdelim(char** ptr,size_t *n,int delimiter,FILE* fp)
5、文件定位
函数接口:
ftell(stream):return the current pos in stream
fseek(stream,long int offset,int whence):change the pos of stream
rewind(stream):set pos in the file's bugin
fgetpos(stream,fpos_t* pos):get the current pos
fsetpos(stream,const fpos_t* pos):set stream's pos
6、文件结束和错误指示器
feof(stream):return 0 if EOF
ferror(stream):return 0 if ERROR
7、流缓冲
(1)、全缓冲:以整个缓冲区为单位进行读写,每次都读写固定 BUFFERSIZE 大小的内容。
(2)、行缓冲:只有遇到行结束标志才进行读写
(3 )、无缓冲:没有设置缓冲
函数接口:
setbuf(fp,char* buf):可以设置 buff 的大小,要关闭缓冲则将 buf 设置位 NULL
setvbuf(fp,char* buf,int type,size_t size):
type 可以位一下之一:
_IOFBUF->全缓冲
_IOLBUF->行缓冲
_IONBUF->无缓冲
可以通过 size 指定缓冲的大小,只有当设置位无缓冲时 size 才无效。
fflush(fp):刷新缓冲区
8、格式 I/O
(1)、格式输出
printf(const char* format...):output to std
1fprintf(stream,const char* format...):output to stream
sprintf(char* buf,const char* format...):out put to buf
(2)、格式输入
scanf(const char* format...):read from std
fscanf(stream,const char* format...):read from stream
sscanf(char* buf,const char* format...):read from array buf.
9、临时文件
tmpnam(char* s)
tempnam(const char* dir,const char* pfx):用户可以自己指定文件路径和文件名前缀
tmpfile(void):生成一个临时文件并且打开它
10、文件描数字的打开、创建、关闭
函数接口:
int open(const char* filename,int flags,mode_t mode/*this is
choosed...*/)
=>这个函数将创建一个文件描数字,并且将这个文件描数字返回,参数位文件名字,打开标

和可选的创建模式,但是 mode 只有在创建文件时才是有效的。
int create(const char* filename,mode_t mode)
=>这个函数将创建一个新的文件,返回创建好的文件的文件描数字,需要指定文件名和创建
文件的 mode,这个函数其实等价于 fd=open(filename,O_WRONLY|O_CREAT|
O_TRUNC,mode)
int close(int filedes)
=>这个函数就是关闭一个文件描数字,需要注意的是当我们调用这个函数关闭一个文
件描数字
的时候,这个不再被使用的文件描数字可以被被再次使用,我们可以想象文件描数字其实
就是一个数组的下标,这个数组就是保存文件项的数组,当一个文件被关闭,那么这个文件所
占有的数组项将可用
ssize_t read(int filedes,void* buffer,size_t nbytes)
=>从已经打开的与文件描数字 filedes 相连接的文件中读取 nbytes 个字节的内容到
buffer 中
ssize_t write(int filedes,const void* buffer,size_t nbytes)
=>向已经打开的与文件描数字 filedes 相连接的文件中把 buffer 中前 nbytes 字节写进

无论是 read 还是 write,都将返回所操作的字节数目,不一定就是 nbytes,有可能遇到
文件尾而不能继续操作...
11、问价描数字的文件位置
函数接口:off_t lseek(int filedes,off_t offset,int whence)
这个函数和 fseek(在流中)是一样的,其中的 whence 只能取下列中一个:
SEEK_SET:从文件开始出开始偏移
SEEK_CUR:从当前文件位置开始偏移
SEEK_END:从文件尾部开始偏移
那么,如何移动文件位置到文件尾部呢? ---> lseek(fd,0L,SEEK_END)
移动到文件开始处可以这样做
---> lseek(fd,0L,SEEK_SET)
-------------------------------
需要注意的一点是,当我们用 lseek 来设置文件位置后,如果设置的文件位置大于当前文件大小,这
也是被允许的,这样的
话,下一次开始写将扩展该文件,这种情况称为在文件中生成了空洞!这些位置将被 0 填充。
【notice 】
(1)、lseek 只是会改变文件的位置,而不会进行任何 I/O 操作,所以它不会改变 inode
中的文件大小,也就是说,即使我们设置的文件位置超出了文件的大小,它也不会导致当前文尾的
改变,只是下次打开文件并且以 O_APPEND 模式下时会自动扩展文件
(2)、每当用 O_APPEND 标志打开文件执行 write 操作时,当前文件首先移动到 inode 给出的文
件大小
2所指定的位置(所以会自动扩展),这也是实现在这种模式下只能从当前文件尾添加内容的依据。
12、dup 和 dup2 函数
函数接口:
int dup(int old):复制 old 文件描数字到最小可用新描数字(返回值)
int dup2(int old,int new):复制 old 文件描数字到 new 文件描数字,如果 new 文件描
数字一句被占用,那么
系统将首先关闭 new 文件描数字,然后再复制过去;如果 old==new,那么只返回 new 但是不
关闭。
什么叫做输入输出重定向?
==>改变一个特定文件描数字对应的文件或者管道
13、fdopen 和 fileno、fcntl
函数接口:
FILE* fdopen(int filedes,const char* opentype):将 filedes 指定的文件描数
字转换位文件流
int
fileno(FILE* stream):获取文件流的文件描数字
int
fcntl(int filedes,int cmd,...):用它可以对一句打开的文件描数字进行各
种操作,例如,重复一个
文件描数字、查询或者设置文件描数字标签、查询或者设置文件描数字状态标签等
|-------------fcntl 命令常数名字及其含
义----------------------------------|
F_DUPFD
重复文件描数字
F_GETFD
获取文件描数字标签
F_SETFD
设置文件描数字标签
F_GETFL
获取文件状态标签
F_SETFL
设置文件状态标签
F_GETLK
获取文件锁
F_SETLK
设置文件锁
F_SETLKW
等待完成的设置文件锁
F_GETOWN
获取收到 SIGIO 信号的进程或者进程组
F_SETOWN
设置....
|--------------------------------------------------------------------
---|
-
14、文件状态标签
什么叫文件状态标签呢? ---->文件状态标签指明文件的打开属性,他们由 open 的 flag 参数指
定。
文件状态标签可以分为三类,如下:
|---------------文件状态标签----------|
(1)、访问方式
-------------------------------------------------------------------
O_RDONLY,O_WRONLY,O_RDWR
-------------------------------------------------------------------
(2)、打开时标志
-------------------------------------------------------------------
O_CREAT
->create the file if not exist now
O_EXCL
->当 o_creat 和此同时设置时,open 失败,保证已经存在的文件不会被覆

O_NONBLOCK ->设置为费阻塞 I/O
O_NOCITY
->...
O_TRUNC
->...
-------------------------------------------------------------------
3(3)、I/O 操作方式
-------------------------------------------------------------------
O_APPEND ->APPEND FILE IN THE END OF THIS CURRENT FILE
O_NONBBLOCK ->...
O_ASYNC
->..
O_SYNC
->..
O_DSYNC
->...
O_RSYNC
->...
-------------------------------------------------------------------
15、什么叫做阻塞 I/O?
调用必须等待所有操作完成,即读写到数据(read)才能返回,非阻塞 I/O 就是:如果操作不能及
时完成,
函数就会返回错误信息。
有两种方法可以指定非阻塞 I/O
(1)、在 open 时指定 O_NONBBLOCK
(2)、对已经打开的描数字,调用 fcntl 来设置
16、readv and writev
函数接口:
ssize_t
ssize_t

readv(int fildes,const struct iovec* iov,int iovcnt)
writev(...):
->writev 将 iov[0],iov[1]...iov[iovcnt-1]指定的存储区中的数据写到 fildes 指定
文件描数字中,返回值是写出的数据总字节数。readv 则按 iov[0]...iov[iovcnt-1]规定
的顺序和长度,分散的从 fildes 中读出数据放到相应的位置里面。
17、文件与目录
什么叫做文件呢?文件由那些部分组成?
答:文件本质上是一个存放数据的容器,文件由数据块和 inode 组成。可以用 stat 命令查看某
个文件的 inode 信息。
18、stat、fstat、lstat 函数
函数接口:
int stat(const char* filepath,struct stat* buf):获取指定文件路径文件的
inode 信息,存在 buf 中
int
lstat(const char* filepath,struct stat* buf):当给定的文件不是符号
链接时,和 stat 一样,否则 stat 将返回
链接所引用的文件 inode,而 lstat 返回链接本身的 inode
int
fstat(int filedes,struct stat* buf):获取指定文件描数字的 inode 信息,
保存在 buf 中
19、文件类型
文件类型由 stat 结构的 st_mode 给出,unix 文件有普通文件、目录、符号链接、特别文件、
FIFO、套接字等。
目录:目录是一种特殊的文件,但是用户不能写目录。目录是由目录登记项组成的一张表,目录中
4的每个文件和子目录在其中有一个登机项,每个
登记项用来映射文件名到它的 inode,结构很简单,一个 inode 号和对应的文件名。
-------------------------------------------------------------------------
-----------------------------------------------------
链接:unix 文件系统提供一种使得多个文件名表示同一个文件的机制,这种机制就称为链接。系统
简单的通过在目录中建立一个新的登机项来表示这种
链接,这个登记项有一个新的文件名字和要链接的 inode 号。一个文件无论有多少个链接,磁盘上
只有一个描述文件的 inode,每个文件的链接数保存在
stat 结构中的 st_nlink 上面,最大允许的链接数量是:LINK_MAX
硬链接(链接)的缺点:
(1)、inode 号是系统内部管理的一个称为 i_list 表格的索引,unix 为每个操作系统维护着一
个 i_list 表格,所以,inode 号在跨文件系统时 inode 号
可能相同,所以硬链接不能进行在跨文件系统之上
(2)、只有超级用户可以创建目录的硬链接
函数接口:
int link(const char* exitingpath,const char* newpath)
----------------------------------------------------------------------------
--------------------------------------------------
符号链接:符号链接是指向例外一个文件的特殊文件,是为了克服硬链接不能跨文件系统的缺陷而
设计的,符号链接不是指向 inode,符号链接有自己的
inode,符号链接保存所指向文件的路径名。而且,符号链接可以指向一个不存在的文件,但是直
到这个文件被创建,这个链接才能有效,当然反过来,
如果一个符号链接所指向的文件被删除了,那么这个符号链接依然存在,相当于指向了不存在的文
件,直到这个文件再次被创建的时候这个符号链接才是
有效的!
函数接口:
int symlink(const char* path,const char* sympath):创建一个符号链接文字
sympath,这个链接文件指向 path
int readlink(const char* pathname,char* buff,int bufsize):读出 pathname
中的内容,保存在 buff 中,然后关闭文件
20、特别文件
什么叫做特别文件呢?
=>特别文件也称为设备文件,特别文件不包含数据,他们的作用是将物理设备映射到文件系统
中的文件名,在 unix 系统中,每一种设备都至少与一个特别文件相
链接,特别文件可以用 mknod 系统调用来创建,并且与内核的一个软件相连,这个软件就称为设备
驱动程序
有两个类型的特别文件。块特别文件和字符特别文件
什么叫做块特别文件呢?
块特别文件与块设备相连,块设备按特定大小并且随机访问的块来执行 I/O,比如硬盘。
什么叫做字符特别文件呢?
字符特别文件与字符设备相连,字符设备可以存储和传递任意大小的数据,有一些字符设备可
以一个字符一个字符的传递数据,比如键盘。
21、文件的类型:文件类型保存在 stat 结构里面的 st_mode 里面,st_mode 包含两种信息,文件类
型和文件方式。
可以用相关的宏来测试是不是属于某种文件
S_ISREG(mode_t m): 普通文件
S_ISDIR(mode_t m): 目录文件
S_ISCHR(mode_t m): 字符特别文件
S_ISBLK(mode_t m): 块特别文件
S_ISLNK(mode_t m): 符号链接
5S_ISFIFO(mode_t m): 管道或者 FIFO
S_ISSOCK(mode_t m):套接字
22、文件的属性和用户组
文件的属性和用户组信息可以在 stat 结构中的 st_uid and st_gid
you can use these function to change the uid and gid of the file:
int chown(const char* pathname,uid_t owner,gid_t group)
int lchown(const char* pathname,uid_t owner,gid_t group)
int fchown(int filedes,uid_t owner,gid_t group)
23、文件访问方式
文件访问方式规定了文件访问权限,一共 9 位文件方式位,他们的含义从左到右分别为:
000 000 000
执行、写、读
分为三组,每组三位,每组中从做到右分别表示可执行、可写、可读。
这三组分别位用户自己、同组成员、其他用户
可以在 stat 里面的 st_mode 下面找到这些信息
----------------------------------------------
S_IRUSR:用户读
S_IWUSR:用户写
S_IXUSR:用户执行
S_IRGRP:...
S_IWGRP:...
S_IXGRP:...
S_IROTH:...
S_IWOTH:...
S_IXOTH:...
------------------------------------------------
24、改变文件创建屏蔽
函数接口:
int chmod(const char* filename,mode_t mode)
int fchmod(int filedes,mode_t mode)
25、检验文件是否具有某种权限:
函数接口:
int access(const char* filename,int how):判断调用进程是否允许按参数 how 指定
的方式访问 filename 指定的文件
其中,how 的取值可以为下列:
----------------------------
R_OK: test the read root
W_OK: test the write root
X_OK: test the exe root
F_OK: test exiting
----------------------------
注意:access 函数是根据进程的实际用户 ID 和实际组 ID,而不是有效用户 ID 和有效组 ID 来检
查调用进程是否具有该权限,如果允许
则这个函数将返回 0,否则将返回-1
26、截断文件
上面叫做截断文件呢?
=>截断文件就是将已经打开的文件缩减成我们指定的长度
函数接口:
6int ftruncate(int filedes,off_t length)
int truncate(const char* pathname,offset_t length)
这两个函数都是将已经打开的文件截断位 length 字节的大小的文件
27、文件的时间
应用可以利用 utime 函数来改变文件的时间,还有一个更加准确的函数是 utimes
函数接口如下:
int
utime(const char* pathname,const struct utimebuf* times)
the struct utimebuf is like that:
struct utimebuf{
time_t actime;//文件的访问时间
time_t modetime;//文件的修改时间
};
int utimes(const char* path,const struct timeval values[2])
the struct timeval is like that:
struct timeval{
long tv_sec;//second num
long tv_usec;//micsecond num
};
28、文件的删除与换名字
函数接口:
int unlink(const char* path):用于删除一个文件名,并且减少该文件名的链接计数,
如果链接计数为 0,文件的内容便被删除
普通用户是不能调用 unlink 来删除一个目录的,但是有其他的函数接口可以解决:
int rmdir(const char* pathname)
int remove(const char* pathname)
29、修改文件或者目录的名字
函数接口:
int rename(const char* oldname,const char* newname):将 oldname 修改位
newname
30、目录操作
工作目录:每个进程都有一个目录与之相连,这个目录就是工作目录,下面的函数接口可以获取或
者修改进程的工作目录
char* getwd(char* pathbuf)
char* getcwd(char* pathbuf,size_t size)
int
chdir(const char* pathname)
int
fchdir(int filedes)
---------------------------------------------------
关于创建目录与删除目录
函数接口:
int mkdir(const char* pathname,mode_t mode)
int rmdir(const char* pathname)
-----------------------------------------------------
读目录流(用户是不可能写目录的,只能读):
需要说明的两种数据结构:
DIR 数据结构:这就是目录的结构
dirent 数据结构:每一个目录项的数据结果,至少包括 inode 号和文件名字
函数接口:
DIR*
opendir(const char* dirname)
struct dirent*
readdir(DIR* dirp)
7int
closedir(DIR* dirp)
31、扫描命令行的参数
值。
函数接口:
int
getopt(int argc,char** argv,const char* optstring)
extern
char*
optarg
extern int
optind,opterr,optopt
说明:参数 optstring 给出合法的选项字符,选项字符后面可以跟着冒号 ‘ : ’ 以表示要求有选项
例如:当 optstring="if:ls" 时表示允许选项 -i,-f,-l,-s,而且-f 后面要求有选项
该函数每次获取一个选项,如果存在选项,则该函数返回获取到的选项,对于那些接受选项值
的选项,
它同时设置外部变量 optarg 指向选项值。
当遇到位置选项字符或者缺少选项值时,getopt 返回 ‘ ? ’ ,同时将该选项字符存储于 optopt 中。
getopt 每次处理 argv 数组中的一项,同时设置外部变量 optind 为下一个要处理的元素的下标,

没有更多需要处理时,函数将返回-1
32、环境表
unix 系统用一个称为环境表的数据结构来表示所有的环境变量和变量值,它是一个指针,该数组的
每一个元素
都指向一个形如 “ name=value”的字符串,name 就是环境变量,而 value 就是环境变量的值
我们可以通过系统定义的全局变量 environ 来引用环境表,该变量称为环境指针,我们需要说明如
下语句来引用
系统的环境表:
extern char** environ;
这样我们就可以查看环境变量及其值了。
当然我们不会这么傻,我们有函数接口来直接获取我们需要的环境变量的值,甚至是设置新的环境
变量和值
函数接口:
char* getenv(const char* name):就是取得名字叫做 name 的环境的值,如果不存在则
返回 null
int
putenv(char* str):设置环境变量,str 的格式形如 “ name=value”
33、终止进程
我们可以使用 exit 函数和_exit 函数来终止进程,但这两个函数有一些不同,exit 会在终止之前
清理一些东西,在调用
_exit 函数返回到内核中,而_exit 函数直接终止进程并且返回到内核,这是很不安全的,不推荐
使用,其次,我们可以
使用 exit(EXIT_SUCCESS) and exit(EXIT_FAILURE)来表示程序是否是正常退出,更加复杂
的退出参数自己扩展把!~
函数接口:
void exit(int status);
void _exit(int status);
那么我们现在来说说 exit 函数吧,既然 exit 函数需要在退出之前做一些事情,那么这些事情是由
什么人来做的呢?
这时候我们就需要知道一个注册函数啦=>
int atexit(void(*func)(void))
参数是一个函数指针,没有参数,以参数 func 调用 atexit 导致 func 所指向的函数被注册为程序
正常终止时要执行的函数
,所有注册的函数以与注册的顺序相反的顺序被调用,并且与调用的次数相同。
834、关于流产程序 abort
真的非常有趣,为什么叫 abort 为流产程序呢?
首先,用 abort 来终止程序肯定是异常终止程序,但是有趣的事情是 abort 函数将在工作目录中生
成一个内存转储文件 core
abort 会在退出之前关闭所有已经打开的流,其实所有的异常终止都是由信号造成的,abort 也是
通过生成一个信号来
流产程序的。例子将在后面介绍......
void abort(void)
35、进程的存储空间
进程的地址空间和 PCB 反应了进程所运行程序的当前状态,当进程用 exec 函数装入一个新进程时,
内核便为这个新的进程
创建了一个新的地址空间,进程的地址空间会有多个组成部分,如下:
正文段:存放程序的执行代码,是只读的所以可以防止被恶意写
初始化数据段:那些声明了而且已经初始化的数据变量,如 int hujian=88888
为初始化数据段:(bss 段),没有初始化的变量,由内核赋予初始值
栈:存放函数内的变量等
堆:用于存放动态申请的变量
36、动态存储与释放
函数接口:
void *malloc(size_t size):这个函数将在申请一块大小为 size 的存储空间,但是这个存储
空间的初始值是不确定的,调用成功则
返回分配的首地址,并且保证是严格对齐的,调用失败则返回 null 并且设置 errno 位 ENOMEM
void *calloc(size_t number_of_elem,size_t elem_size):很明显这个函数用来为结构
数组分配空间,第一个参数为数组的大小,
第二个参数位数组元素的大小,分配成功将用 0 来填充,并且返回指向第一个元素的指针
void* realloc(void* ptr,size_t newsize):用于增加或者减少已经分配的内存大小,第
一个参数是已经获得的内存指针,第二个
参数是要重新分配的内存大小,如果连续的延伸能够满足继续分配,函数将不会移动数据,但是如
果不能满足的话,那么该函数将重新
寻找一块内存,并且将数据搬迁到新的数据块上面,然后返回新存储块的指针,所以千万注意不要
丢失指针喔!
void* memalign(size_t size,size_t bound):这个函数是由用户字节定义对齐字节,第一
个参数是要申请的内存大小,第二个参数是
对齐的字节
void* valloc(size_t):该函数只是对 memalign 函数的特殊使用,对齐是在页边界,实现如下:
void* valloc(size_t size){
memalign(size,PGSIZE);
}
37、一个很好玩的函数(两个)
函数接口:
int
setjmp(jmp_buf env):设置返回点
void
longjmp(jmp_buf env,int val):返回 env,val 将作为 setjmp 的返回值,如
果为 0,则 setjmp 仍然返回 1
虽然很好玩,但是因为和编译器密切相关,一般应用很难用到,就不再赘述啦~
38、进程资源
(1)、上面叫做资源呢?简单的讲资源就是能够影响进程的一些因素,比如进程最大存储空间,可
使用的 CPU 时间等等,如果
这些东西没有限制的话,那么一个进程就舒服了,但那肯定是不可能的,我们能做上面呢那?我们应
该知道的一个事实就是,资源
是有限制的,那系统规定的资源限制的最大值称为硬限制,我们不能超过这个限制,但是我们可以
修改我们自己的进程的最大限制,
我们修改的最大限制称为软限制,但是软限制的最大值不能大于硬限制,我们可以减小我们自己的
硬限制,但是这是不可逆转的!
9(2)、我们可以查看和设置资源限制
函数接口:
int
getrlimit(int resource,struct rlimit* rlptr):很明显这是我们获取资
源限制的函数啊
int
setrlimit(int resource,const struct rlimit* rlptr):很明显是设置资
源限制啊
我们需要知道的结构:
struct rlimit{
rlim_t rlim_cur;//这是资源限制的软限制
rlim_t rlim_max;//给出资源限制的最大值,也就是硬限制
};
对了,rlim_t 其实就是一个长整形...
对了还有,函数里面有一个参数叫做 resource,那是上面呢?就是资源啊,资源可以取下面这些
常数:
-------------------------------------------------------
RLIMIT_AS:进程可用虚拟空间的最大字节数
RLIMIT_CORE:core 文件的最大字节数
RLIMIT_CPU:进程能够使用的 CPU 时间
RLIMIT_DATA:进程数据段空间的最大值(初始化数据段,为初始化数据段,堆空间之和的最大
值)
RLIMIT_FSIZE:进程能够创建的最大文件的字节数
RLIMIT_MEMLOCK:可以用 mlock 和 mlockall 锁住在物理存储器的最大虚拟存储字节数
RLIMIT_NOFILE:进程最多能够打开的文件个数
RLIMIT_NPROC:最多能够创建的子进程个数
RLIMIT_RSS:进程应当得到的最大物理内存页数
RLIMIT_STACK:栈的最大字节数
RLIMIT_NLIMITS:系统中不同资源个数
RLIMIT_INFINTY:展示一个常数,表示对指定的资源没有限制
----------------------------------------------------------
(3)、资源使用统计
函数接口:
int
getrusage(int who,struct rusage* rusage):报告当前进程或者该进程的所
有已经终止并且要等待的子进程
的资源使用情况,参数 who 指明是当前进程还是子进程,只能取如下两个值:
---------------------------
RUSAGE_SELF:调用此函数的进程
RUSAGE_CHILDREN:该进程所有已经终止并且在等待的子进程
---------------------------
39、用户信息
用户名:函数接口:char* getlogin(void):返回用户名
用户数据库:
用户数据库将保存所有的用户信息,在现在的 unix 中有两个文件属于保存用户信息的:
/etc/passwd and /etc/shadow
前者称为口令数据库文件,对所有用户开放。后者称为口令影子文件,只有特权用户才能访问。
这连个文件中,没一行都由七个区域组成,两个区域之间用冒号隔开,格式如下
------------------------------------------------------------------
user_name:command(x means no
command):user_id:user_group_id:user_name,address,telephone_job,telephone_hom
e:user_dir:shell
这七个域由一个叫做 passwd 的结构来表示:
struct passwd{
char* pw_name;
char*
pw_passwd;
uid_t pw_uid;
gid_t
pw_gid;
10char*
pw_gecos;
char*
pw_dir;
char* pw_shell;
}//end of struct
我们有两种方式来读取数据库文件,第一种是给定用户名或者用户 id 的情况下查找特定的一项,如
果没有则返回 null
struct passwd*
getpwuid(uid_t uid):根据 uid 来查找信息
struct passwd*
gtpwnam(const char* name):根据用户名来查找信息
下面一种方法就是扫描整个数据库,一个一个用户的读所有用户的数据项
函数接口:
struct passwd*
getpwent(void)
void
setpwent(void)
void
endpwent(void)
有趣的是,每次调用 getpwent,都是指向下一个信息,第一次调用是返回首个信息,然后再
次调用则返回
下一个信息,如果没有了,就返回 null
setpwent 和 endpwent 分别打开和关闭数据库
------------------------------------------------
struct passwd* getpwnam(const char* name)
{
struct passwd* ptr;
//open the database
setpwent();
while((ptr=getpwent())!=NULL){
if(strcmp(name,ptr->pw_name)==0){
break;
}//end if
}//end while
//close the database
endpwent();
return (ptr);
}
--------------------------------------------------
组数据库:
和用户数据库一样 /etc/group and /etc/gshadow 是组数据库,前者包含每个组的信息,
后者包含
系统中所有组的信息每个组对应一行,每一行有四个域,中间有冒号隔开:
如:user:x:500:hujian,libai
lib:x:190:hujian
第一个域是组名,第二个是组 id,第三个域是口令,没有则位 x,第四个是组成员,成员间用
逗号隔开,简单说一下,hujian 不仅属于
user 这个组,还属于 lib 这个组,user 是基本组,lib 是附加组
函数接口:
在看函数接口之前,先看看 group 数据结构:
struct group{
char* gr_name;//group name
char*
gr_passwd;//group passwd
int
gr_gid;//group id
char** gr_mem;//point to member of group
};
下面就是函数接口:
struct group*
getgrgid(gid_t gid):get the group by gid
11struct
struct
void
void
group*
getgrnam(const char* name):get the group by name
group*
getgrent():one by one...
setgrent():open the database
endgrent():close the database
获取附加组 id
函数接口:
int
getgroups(int gidsetsize,gid_t group_list[]);
该函数把调用进程的附加组 id 填进 group_list 中,第一个参数则是限定第二个参数的大小,
实际的附加组个数
由函数返回,如果只是想直到附加组的个数,那么就指定第一个参数位 0,那么第二个参数就不
会被填充。
40、进程的身份凭证
unix 系统提供了三组 id 来表示进程身份凭证
----------------------------------------------
第一组:指明实际用户是谁
->实际用户 id,实际组 id
第二组:用于操作和文件访问权限测试
->有效用户 id,有效组 id,附加组 id
第三组:由 exec 保存
->保存的调整用户 id,保存的调整组 id
-----------------------------------------------
相关说明:
=>实际用户 id 和实际组 id:由 login(1)程序在用户注册时设置。
=>有效用户 id 和有效组 id:是进程当前起作用的 id,也称为现行用户 id 和现行组 id,exec 加
载执行文件时,有效用户 id
和有效组 id 默认设置成实际用户 id 和实际组 id,但是如果文件设置了调整用户/组 id 位以指明该
文件是要以调整用户
id 或者是调整组 id 的方式来执行的话,exec 加载该文件时会将有效用户 id 或者是有效组 id 改为
文件拥有者的用户 id
或者组 id。
有效用户 id 和有效组 id 影响文件的创建和访问,文件创建期,内核将文件的拥有者设置为进程的
有效 id 和有效组 id,
文件访问时,内核根据进程的有效 id 和有效组 id 来判断进程是否有权限访问文件。
=>保存的调整用户 id/组 id:exec 在调整进程 id 之前,先保存进程的当前有效 id,以后可以恢
复。
函数接口:
uid_t
uid_t
gid_t
gid_t
gid_t
getuid():返回进程的实际用户 id
geteuid():返回进程的有效用户 id
getgid():返回进程的实际组 id
getegid():返回进程的有效组 id
getgroups(int count,gid_t groups[]):获取进程的当前附加组 id
关于调整进程的身份:
进程总是可以调整它的有效用户 id/组 id 回到实际用户 id/组 id
函数接口:
int setuid(uid_t uid):如果进程有特权,那么设置进程的实际用户 id 和有效用户 id
为 uid,否则只设置有效
用户 id 为 uid,而且 uid 要么等于实际用户 id,要么等于保存的调整用户 id
int setgid(gid_t gid):same as setuid
int setreuid(uid_t r_uid,uid_t e_uid):-1 代表不设置,其他情况都设置
int setregid(gid_t r_gid,gid_t e_gid):same as setreuid
int seteuid(uid_t uid):只设置有效用户 id,而且要有特权才可以设置位其他,否则只
能设置位实际用户 id 或者保存的调整用户 id
12int
setegid(gid_t gid):same as seteuid
获取进程当前有效用户 id 的用户名:
函数接口:
char*
cuserid(char* string)
注意:getlogin 得到的是运行进程的用户注册名,cuserid 得到的是与进程有效用户 id 相
连的用户名。
41、进程标志
unix 中每个进程都有一个唯一的进程标志,称为进程 id 或者 pid,pid 的类型是 pid_t,它是整
数类型
函数接口:
pid_t getpid():可以获得进程的 pid
pid_t
getppid():可以获得父进程的 pid
关于进程创建:
pid_t
fork():由进程 fork 出一个和自己一模一样的进程,fork 成功时,子进程的返回
值为 0,父进程的返回值
是新创建的子进程的 pid,若 fork 失败,则返回-1 并且将设置 errno 以告知错误信息。
注意,fork 会返回两次,一次是返回子进程,一次是返回父进程,需要用 if 来判断是返回哪
个进程。
if(pid==0) child else father.
42、fork 出来的子进程和父进程之间的区别
相同点:实际用户 id,实际组 id,有效用户 id,有效组 id,附加组 id,会晤期 id 和控制终端,
调整用户 id 标志和调整组 id 标志,当前
工作目录和根目录,文件创建屏蔽,信号屏蔽与设置,任何打开的文件描数字和执行时关闭标志,
环境变量,所有相连的共享存储段,
资源限制。
不同点:有自己唯一的 id,有各自不同的付父进程 id,进程操作文件描数字不会影响父进程,但文
件位置相同,子进程耗费的时间量
被设置为 0,子进程不继承父进程的文件锁,定时器,任何悬挂信号将被清除,但继承信号屏蔽和信
号动作。
vfork:
pid_t
vfork():该函数和 fork 函数一样,不同的有两点,fork 创建进程的整个地址空
间副本,而 vfork 不复制这一个副本,
第二个是 fork 允许父进程和子进程相互独立的运行,但是 vfork 不允许,vfork 在借出父进
程的地址空间给子进程的同时阻塞
父进程,父进程被悬挂直到子进程调用了 exec 或者_exit,此时内核返回地址空间给父亲进程
并且将其唤醒。
现在我们应该直到一件事情就是,什么叫做 “ 写- 复制 ” ,用这样的方法时,父进程的数据和栈空间临
时称为只读的并且标志位 “ 写- 复制 ” ,
子进程一开始与父进程共享存储页,如果子进程或者父进程企图修改某页时,缺页中断将会出现,
于是复制该页成为新的可写页,这样,
只有那些修改了的也需要复制,而不是复制整个地址空间,如果子进程调用 exec 或者 exit,这些
也将会转换为他们原来的保护并且将
“写- 复制 ” 去掉。
43、执行程序(装载程序)
exec 函数组:
int execl(const char* path,const char* arg0,...,(char*)0):
int
execlp(const char* file,const char* arg0,...,(char*)0):
int
execle(const char* path,const char* arg0,...,const char*
envp[]):
13int
int
int
envp[]):
execv(const char* path,const char* argv[]):
execvp(const char* file,const char* argv[]):
execve(const char* path,const char* argv[],const char*
一点解释:p 结尾的函数意味着第一个参数是文件名 file,并且使用 PATH 环境变量来寻找可执行
文件。
l 结尾的函数意味着函数中含有新程序的参数表
e 结尾的函数具有 env 数组作为参数,而不适用当前的环境变量表
44、等待进程完成
函数接口:
pid_t
wait(int *stat_loc):该函数首先检查调用进程是否有任何已经终止了的子
进程,
如果有的话,它立刻返回,如果没有的话,wait 将阻塞调用进程直到有一个子进程结束,
wait 将返回
已经终止的子进程的 pid,并且将该进程的终止状态存储在 stat_loc 中,当调用进程同时有
多个子进程
终止时,wait 将任意返回一个,如果调用进程没有任何子进程,函数将返回-1,并且设置
errno
下面是一些用于检查 wait 或者 waitpid 返回状态的宏定义:
-----------------------------------------------------------------------
WIFEXITED(stat_val):正常终止,则位真
WIFSIGNALED(stat_val):异常终止,值为真
WIFSTOPPED(stat_val):是被停止的子进程,则为真
WEXITSTATUS(stat_val):可以获得个体 exit 传递的出口状态
WTERMSIG(stat_val):引起子进程终止的信号数量
WSTOPSIG(stat_val):引起子进程停止的信号数
------------------------------------------------------------------------
45、通过两次调用 fork 来避免僵死进程
原理:
第一个子进程调用 fork 生成另一个子进程来执行程序并先于第二个子进程
而终止,由于第一个子进程的父进程调用了 waitpid,它不会成为系统的
僵死进程。而第二个子进程没有调用 wait 来等待,但是由于父亲进程(第
一个子进程)先于它而终止使得它被 init(pid=1)继承,这时它是活跃的
所以不会称为僵死进程,当它要终止时,init 进程将调用 wait 释放它的
proc 结构,这是多么神奇的事情啊
46、system 函数
这是一个集成的函数,它可以实现由一个程序执行另一个程序
函数接口:int sysytem(const char* command)
47、获取和设置进程组
什么叫进程组呢?
我们只需要直到进程组有唯一的进程组 id,每个进程组有一个进程组长,进程组长的进程 id 也就
是进程组
的进程 id,下面就是函数接口:
pid_t
getpgrp():获取进程组的 id
int
setpgid(pid_t pid,pid_t pgid):改变进程 pid 的进程组 id 为 pgid
48、会晤期
会晤期是一个或者多个进程组的集合,每一个进程属于一个会晤期和一个进程组。每个会晤期有一
个会晤期主席,它就是
14创建会晤期的那个进程,下面是一个可以创建一个会晤期的函数接口:
pid_t setsid();
需要注意的是,当会晤期主席终止时,它会结束会晤期。
49、控制终端
每一个会晤期可以有一个控制终端,进程可以通过控制终端进行输入输出等操作。
需要注意的几点是:
每个进程可以有一个控制终端。
每个会晤期可以控制终端也可以没有控制终端。
下面这个函数将可以得到控制终端的文件名:
char*
ctermid(char* ptr):将保存在 ptr 中,但是如果 ptr 是空指针的话,那么该
函数将返回得到的
控制终端文件名,一般是 /dev/tty
50、信号处理
上面叫做信号呢?
在 unix/linux 里面,信号是作为通知进程发生了某种事件的手段,并且其发生常常与进程当
前的活动无关,信号也称为
软终端,它提供了一种处理异步事件的方法,产生信号的原因有很多,比如:
(1)、用户按下了某个终止键
(2)、程序异常
(3)、kill 函数允许进程发送任何信号给其他进程或者进程组
(4)、当发生了某种必须让进程知道的事件时,也会产生信号
(5)、企图读写终端的后台进程会得到作业控制信号
(6)、当进程超越了 CPU 或文件大小的限制时
生成信号的事件可以归为三类:程序错误,外部事件,显示请求
信号的生成可以是同步的也可以是异步的,同步信号与程序中的某个操作有关并且在那个操作
进行的同时产生,异步信号
是进程之外的事件生成的信号,一般外部事件总是生成异步信号。
当一个信号到达进程时,进程可以选择如下动作来处理到达的信号:
(1)、忽略信号
(2)、捕获信号
(3)、执行系统默认动作
(4)、流产
(5)、终止
(6)、忽略
(7)、挂起
(8)、继续
一些术语:
生成信号(发送信号):信号出现
交付(接收):进程识别了信号
悬挂:信号已经生成,但是还没有交付
对于每一种信号,在某个时刻它对应的动作安排称为信号的布局。
信号屏蔽字:每个进程有一个信号屏蔽字,就是来限制信号的识别,只识别我们需要的信号就可以
了。
51、打印信号数对应的描述信息
函数接口:
void psignal(int signo,const char* msg):
52、产生信号
15的
函数接口:
int
raise(int sig):简单的发送信号 sig 给调用它的进程,如果安装了信号 sig
信号句柄,则 raise 函数将在句柄函数返回之后才返回
int
kill(pid_t pid,int sig):向进程 pid 发送信号,sig 为 0 则表示空信号,空信
号一般用来
检测 pid 的合法性,如果进程 pid 不存在的话,向这个进程发送空信号是不会成功的,函数将
返回-1 并且设置
errno 信号,函数成功则返回 0,,参数 pid 可以是下面的四种情况之一:
(1)、pid>0:信号发送给进程号为 pid 的进程一个 sig 信号
(2)、pid==0:发送给所在进程组的所有进程 sig 信号
(3)、pid<-1:信号发送给进程组 id 为 |pid| 并且发送者有权向他发送信号的进程一个
sig 信号
(4)、pid==-1:广播信号,到所有有权向他发送该信号的进程
53、设置信号的动作
首先给出函数接口:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler):参数 signum 指
明是哪一种信号,handler 就是我们
要做的动作,可以是下列三种情况之一:
(1)、SIG_DFL:采用默认动作,由系统指定动作函数
(2)、SIG_IGN:忽略该信号,但是有一些信号是不能忽略的,比如 SIGKILL,SIGSTOP
(3)、信号句柄,也就是我们自己定义的函数指针
需要注意的是,信号句柄应该是一个只有一个参数而且没有返回值的函数,参数指明信号的类
型。
当 signal 函数调用成功时,返回值就是上述三种情况之一,如果调用出错,那么它将返回
SIG_ERR
并且设置 errno。
54、sigaction 函数
函数接口:
int sigaction(int signum,const struct sigaction* act,struct
sigaction*oact):
struct sigaction{
void
(*sa_handler)();
void
(*sa_sigaction)(int,siginfo_t*,void*);
sigset_t sa_mask;
int
sa_flags;
};
和 signal 函数一样,sigaction 函数的第一个参数用来指定信号,第二个参数是新的信号动
作,为 null
时则不改变信号的动作,第三个参数将获得老的信号动作。
55、一个编译器的修饰符
volatile:告诉编译器这个变量会在某些地方可能会被修改,所以编译器就不会对这个变量做
一些不必要的优化。
56、sigset_t 类型和信号集操作
信号屏蔽是被阻塞的信号集合,unix 中表示信号集合的数据类型是 sigset_t,下面是操作信号集
合的函数接口:
int sigemptyset(sigset_t *set):初始化信号集合为空,即不包含任何信号,总是返回
0
16int sigaddset(sigset_t* set,int signo):向 set 中增加 signo 信号,成功返回 0,
失败返回-1
int sigdelset(sigset_t* set,int signo):从 set 中删除 signo 信号,成功返回 0,
失败返回-1
int sigismember(const sigset_t* set,int signo):测试 signo 是否在 set 中,若
在返回 1,不在返回 0,失败返回-1
57、设置信号屏蔽
函数接口:
int
sigprocmask(int how,const sigset_t* set,sigset_t *oset):用于改变
或者测试调用进程的信号屏蔽。
如果 set 不是 null 的话,它指向用于改变信号屏蔽的信号集合,此时参数 how 指明如何改变
信号屏蔽,oset 将返回原先
的信号屏蔽,下面是 how 的可取值:
(1)、SIG_BLOCK:阻塞 set 所指向集合的信号,即将它们加入到当前的信号屏蔽中
(2)、SIG_UNLOCK:放开 set 所指信号集合中的信号,即将它们从当前信号集合中除去
(3)、SIG_SETMASK:用 set 所指项的信号集合作为新的信号屏蔽集合,抛弃原来的信号屏

58、检查悬挂信号
函数接口:
int sigpending(sigset_t* set):将悬挂信号保存在 set 中
59、等待信号
函数接口:
int
pause(void):仅仅是等待信号出现,放在循环里面用,用来等待信号出现
int
sigsuspend(const sigset_t* sigmask):用参数所指项的信号集合临时替代调
用进程的信号屏蔽,
然后挂起调用进程直到有不属于 sigmask 的信号到达
60、信号栈
所谓信号栈就是信号句柄所在的栈空间,一般情况下信号栈和用户栈在一个位置,所以不安全,
我们需要
建立自己的信号栈来安放信号句柄。
函数接口:
int
sigaltstack(const stack_t* ss,stack_t* oss):很明显 ss 是我们自己指定
的信号栈,oss 是原来的信号栈
stack_t 至少包含一个栈开始地址,栈大小,和栈状态标志,对于栈状态标志,只能取下面两
种情况:
SS_ONSTACK:表示栈将开始生效,ss_sp+ss_size 这块空间就是我们定义的信号栈
SS_DISABLE:这表示不用此栈,此时忽略 ss
函数成功成功返回 0,失败返回-1,这个函数只是准备好了一个信号栈,至于哪些信号能用这
个信号栈,我们需要在
用 sigaction 函数时通过 SA_ONSTACK 标志来指定。
61、关于时间的函数接口
time_t
time(time_t* tloc):返回从 unix 纪元开始的总秒数,如果 tloc 不是空指针,
那么时间也可以保存在 tloc 里面
double
difftime(time_t time_current,time_t time_start):计算两个时间之间的
间隔秒数
int
gettimeofday(struct timeval* restrict tp,void *restrict tzp):
struct
timeval{ time_t tv_sev;suseconds_t tv_usec;};
上面这个函数将返回更加精确的时间,保存在 tp 中。
int
settimeofday(const struct timeval* tp,const void* tzp):设置时间,tzp
在 linux 下应该位空。
int
adjtime(const struct timeval* delta,struct timeval*
olddelta):这个函数用来调整系统时钟,
delta 就是我们需要调的时间数量,为负数表示要调慢,正数表示要调快。
17struct tm* gmttime(const time_t* time):将 time 转换为可读形式
struct tm*
localtime(const time_t* time):得到本地时间
time_t
mktime(struct tm* brokentime):将可读的时间转换为秒
62、格式化日期与时间
char*
asctime(const struct tm* tmptr):返回字符串形式的时间
char*
ctime(const time_t* timeval):是将原始日期转换为可读形式,返回结果和
asctime 一样
size_t
strftime(char* s,size_z maxsize,const char* fomat,struct tm*
timeptr):
这个函数和 sprintf 函数一样,将时间 tmptr 按照 fomat 的格式保存在 s 数组中,maxsize 为 s
的大小,函数将返回实际
转换的字符数量,但是不包括终止符号,所以如果 maxsize 等于返回值,那么说明数组大小太小了。
char*
strptime(const char* buf,const char* fomat,struct tm* timeptr):
这个函数是第一个函数的逆函数
63、CPU 时间和墙钟时间
程序的运行时间有 CPU 时间和墙钟时间,CPU 时间就是程序占用 CPU 的时间,是一个固定值,
墙钟时间表示进程从开始到
运行结束的时间,可能包含了其他进程运行的时间,所以不能用墙钟时间来表示程序的性能。
我们可以用下面的函数接口来实现得到 CPU 时间,其实 time 函数只能得到墙钟时间:
clock_t
clock():返回进程的当前 CPU 时间,包括用户时间和系统时间,类型
clock_t 表示系统内部的
时间单位(1/CLOCKS_PER_SEC),CLOCKS_PER_SEC=1000000,为了统计程序运行的 cpu
时间,一般在程序的开始和结束
处调用 clock,然后两次调用的结果相减。
64、times 函数
函数接口:
clock_t
times(struct tms* buffer):此函数和 clock 函数一样返回时间,但
是返回的时间更加精确,函数
的返回值为当前的墙钟时间,我们应该知道的事情是,系统时钟发出一次中断信号称为一个滴
答,我们可以用函数
sysconf(_SC_CLK_TCK)来得到这个滴答值,我们来看看 tms 的信息:
struct
tms{
clock_t
tms_utime:用户态下执行进程用去的 CPU 时间
clock_t
tms_stime:系统为进程服务用去的时间
clock_t
tms_cutime:子进程的 tms_utime+tms_cutime
clock_t
tms_cstime:子进程的 tms_stime+tms_cstime
};
65、睡眠函数
unsigned int sleep(unsigned int seconds):睡眠 seconds 秒或者捕获了某种信号
并且信号句柄已经返回
66、设置定时器
每一个进程都有三个独立无关的间隔定时器:
墙钟定时器(ITIMER_REAL):到期会发送 SIGALRM 信号个给进程
虚拟定时器(ITIMER_VIRTUAL):仅当进程运行在用户态时才走动,会发送 SIGVTALRM 信号
剖面定时器(ITIMER_PROF):用户态或者内核态都会走,会发送 SIGPROF 信号
函数接口:
unsigned int alarm(unsigned int seconds):一次性的定时器,只能设置墙钟定时器,
每一个进程只有一个
墙钟定时器,再次设置时将返回上一次未完成的秒数。
18int
setitimer(int which,const struct itimerval* value,struct
itimerval *oldvalue)
int
getitimer(int which,struct itimerval* value)
struct
itimerval{
struct timeval it_interval;//定时间隔时间
struct timeval it_value;//定时开始时间
};
参数 which 指明是哪一种定时器,value 就是我们设置的定时器,它是一个连续的定时器,函
数将会返回上一个
定时器在 oldvalue 里面,第二个函数可以得到定时器
67、实时时钟与设定
unix 里面设定有多种类型的时钟,每一种时钟用时钟 id 来标志,这个时钟 id 是
clockid_t(int),在 linux 中,有下列的时钟:
CLOCK_REALTIME:这是系统范围内的时钟
CLOCK_MONOTONIC:在 linux 里面,是从系统启动以来的时间,不能改变
CLOCK_PROCESS_CPUTIME_ID:这是进程范围的时钟,他给出进程特定的 cpu 时间
CLOCK_THREAD_CUPTIME_ID:这是线程范围的时钟,给出线程特定的 cpu 时间
struct timespec{
time_t tv_sec;//seconds
long
tv_nsec;//nseconds
};
函数接口:
int
clock_getres(clockid_t clock_id,struct timespec *res):得到特定时
钟的分辨率,出错将返回 1
int clock_gettime(clockid_t clock_id,struct timespec* res):得到当前时间
int clock_settime(clockid_t clock_id,const struct timespec* res):设定时

68、实时睡眠
函数接口:
int
nanosleep(const struct timespec* req,struct timespec* rem):和
sleep 一样,剩余时间将存在 rem 中,成功返回 0,失败返回-1
69、创建和删除实时定时器
int timer_create(clockid_t clockid,struct sigevent* restrict
evp,timer_t* restrict timerid):
第一个参数是时钟类型,第二个参数用于设定支持此定时器的信号交付,第三个参数存放生成
的定时器 id
int
timer_delete(timer_t timeid):删除 timeid
70、设置实时定时器
int
timer_gettime(timer_t timerid,struct itimerspec
*value):value 中将保存定时器剩余时间
int
timer_settime(timer_t timeid,int flags,const struct
itimerspec* restrict value,
struct itimerspec* restrict ovalue):就是设置,flags=TIMER_ABSTIME
71、定时器超期计数
什么叫做超期计数呢?
就是定时器信号没有得到及时处理的次数。
int
timer_getoverrun(timer_t timeid);
1972、与终端设备有关的两个函数接口
int
isatty(int fd):如果文件描述字 fd 与一打开的终端设备相连,则函数返回
1,否则返回 0
char* ttyname(int fd):如果文件描述字 fd 与一个打开的终端设备相连,则返回那
个终端的名字,否则返回 NULL
73、文件锁
文件锁有读锁和写锁:读锁可以是多个,他的目标是防止其他进程来跟改正在读的文件内容。读锁
也被成为 “ 共享锁 ” 。
写锁只能有一个。他的作用是隔离文件使得他所写的内容保持一致,有互斥锁的区域不能有其他锁。
所以写锁也被称为 “ 互斥的锁 ” 。
函数接口:
int
fcntl(int filedes,int cmd,struct flock* lock):
struct flock{
short l_type;//the lock's type,can be =>F_RDLCK,F_WRLCK,F_UNLCK
off_t l_start;//offset
short l_whence;//where ->SEEK_SET,SEEK_CUR,SEEK_END
off_t l_len;//[l_start,l_start+len]
pid_t l_pid;//the process
};
cmd 可以取得几个命令值:
F_GETLK,F_SETLK.
74、当 len=0 时,他允许锁住文件的任意区域,解这类锁时,需要注意,当我们扩充了文件内容之后,
解锁时的区域长度应当包括新扩充的部分。
75、
我们需要知道的一些关于文件锁和文件与进程之间关系的内容:
(1)、锁是和进程相连接的,我们需要知道什么叫锁,及其设置锁的目的是什么,我们设置锁的目
的就是为了不让别的进程来和自己这个进程
一起访问或者修改文件的内容,所以,就算时 fork 出来的子进程也不会继承父进程的文件锁,
这也是很好理解的。
(2)、锁之和文件相连而不和文件描数字相连,尽管我们设置锁时使用的是文件描数字。这意味着
当进程用描数字来关闭一个文件时,他在此
文件上设置的所有锁都将被释放,即使这些锁是这个进程用其他文件描数字设置的。
(3)、进程终止时,系统会关闭这个进程打开的文件,从而释放该进程设置的所有文件锁。
76、建议锁和强制锁
fcntl 函数不仅可以提供建议锁机制,还可以提供强制锁机制。
建议锁就是建议,内核并不管你是不是遵守建议锁机制,read 或者 write 可以凌驾在建议锁机制
之上。
也就是说,建议锁就是用户自己的锁机制,和内核没有关系,所以在使用建议锁的时候,我们应该
遵守
建议锁的机制。而强制锁则由内核管理,排除一切不合法的文件访问与操作。
强制锁通过设置 id 来设置。
77、信号驱动的 I/O
所谓信号驱动的 i/o,也就是异步 i/o,但本质上还是同步 i/o。
采用信号驱动 i/o,当在描述字上有数据到达时,进程会收到一个信号,此时对该描数字进行输入
20输出不会阻塞。
linux 上有两个可用于信号驱动的 i/o 的信号:
SIGIO,SIGURG,后者只用于通知网络链接上到达了带外数据。
如果在文件描数字上设置了 O_ASYNC,则当该描数字上由 i/o 活动时会生成 SIGIO 信号,另外,对
于套接字,当其上
到达带外数据时,总是会生成 SIGURG 信号。
进程可以调用 fcntl 函数来指定接收信号的进程或者进程组,当然也可以获取接收信号的进程或者
进程组:
(1).set the process/process group for the signal
int fcntl(filedes,F_SETOWN,pid):pid 为正数时,表示进程 id,负数表示进程组 id,出错
返回-1
int
fcntl(filedes,F_GETOWN):返回进程或者进程组,正数是进程,负数是进程组
实现信号驱动的 i/o 的步骤应该为:
(1)、用 signalaction 来建立信号句柄
(2)、用 fcntl 来设置接收信号的进程或者进程组
(3)、如果要接收的是 SIGIO 信号,则需要用 fcntl 与 F_SETFL 来设置文件描数字的 O_ASYNC
标志使得能生成该信号。
78、多路转接 i/o
不少应用需要同时接收来自多个输入通道的输入,并且只要有输入到达就必须接收,这类应用应该
多数是时间驱动的应用。
函数接口:
int select(int nfds,fd_set* rfds,fd_set* wfds,fd_set* efds,struct
timeval* timeout);
select 函数告诉内核,调用他的进程需要等待多种 i/o 事件,并且仅当这些事件中的一个或
者多个出现时,或者
指定的时间已经过去时,才会唤醒调用他的进程。
我们先来解释一下最后一个参数的意义:
(1)、当 timeout=null 时,表明进程愿意永远等待没有事件限制
(2)、当 timeout=0 时则正好相反,表示进程不想等待,检查描数字后立即返回
(3)、当 timeout>0 时,表面进程愿意等待一段时间
nfds 参数指明要测试的描数字的个数,最多可以用宏定义 FD_SETSIZE 来获得
参数 rfds,wfds,efds 分别给出要求内核测试的读、写和另外条件的描数字集合
一些宏定义:
void FD_ZERO(fd_set* fdset):初始化为空集合
void FD_CLR(int filedes,fd_set* fdset):将 filedes 从所描述的集合中清除
int
FD_ISSET(int filedes,fd_set* fdset):判断是否属于,返回 1 表示属于,
0 表示不属于
void FD_SET(int filedes,fd_set* fdset):添加描述字集合
关于函数返回值:
(1)、返回值大于 0,指出三个描数字集合中已经就绪的描数字个数
(2)、等于 0,意味着在有描数字就绪之前计时器已经到期
(3)、-1,意味着出现了错误
所以,只有核实了 select 函数返回值大于 0 时检查集合才有意义。
79、函数 poll
函数接口:
int poll(struct pollfd fds[],nfds_t nfds,int timeout):
poll 和 select 函数在本质上是一致的,poll 检查一组文件描数字以查看是否有任何悬
挂事件,并且可以有选择
的为等待某个描数字上的时间而指定时间。
下面我们看看 pollfd 这个结构:
struct pollfd{
int
fd;//要检查的描述字
short
events;//感兴趣的事件
short
revents;//fd 上发生的事件
21};
poll 将返回 revents 的值,events 和 revents 的取值可以如下:
POLLIN:有可读的数据
POLLPRI:有可读的高优先数据
POLLOUT:可以无阻塞的写数据
POLLERR:出现错误
POLLHUP:出现挂起
POLLNAVL:描数字引用的不是已经打开的文件
fds 指定 nfds 的元素个数,timeout 和 select 一样。
80、异步 i/o
首先我们应该知道什么叫同步和异步 i/o。
同步 i/o:同步 io 操作导致发出请求的进程被阻塞直到 io 操作完成
异步 i/o:异步 io 操作在 io 期间不导致发出请求的进程被阻塞
下面是一些异步 io 函数:
----------------------------------------------------------------
aio_read: 对指定的文件描数字启动读请求
aio_write:
对指定的文件描数字启动写请求
aio_error:
返回指定操作的错误状态
aio_return: 返回已经完成操作的状态
aio_suspend: 悬挂调用进程直到指定的请求中至少有一个完成
aio_cancel: 删除悬挂在文件描数字上的一个或者多个请求
lio_listio: 启动一组 io 请求
aio_fsync:
异步的将系统缓冲区中含义的文件已经修改的数据写到永久存储
----------------------------------------------------------------------
异步 io 控制块:这是一个数据结构,用来传递给内核所需要的信息,因为异步 io 是由内核完成的。
我们就来看看这个数据结构吧:
struct aiocb
{
int
aio_fildes;//要读写的文件描数字
off_t
aio_offset;//文件位置
volatile void* aio_buf;//数据缓冲区的地址
size_t
aio_nbytes;//要传递的字节数
int
aio_reqprio;//请求优先位移
struct sigevent
aio_sigevent;//信号结构,我们待会再说
int
aio_lio_opcode;//要执行的操作,只用与 lio_listio
}
aio_sigevent 用于指定当异步 io 操作完成时以什么方式通知进程,他另外一个结构:
struct sigevent
{
union sigval
sigev_value;//应用定义的值
int
sigev_signo;//要生成的信号
int
sigev_notify;//通知类型
void(*sigev_notify_function)(union sigval);//通知函数
pthread_attr_t*
sigev_notify_attributes;//通知属性
}
其中,通知类型如下:
SIGEV_NONE:事件发生时不产生信号
SIGEV_SIGNAL:信号发生时将生成信号
SIGEV_THREAD:信号发生时将执行指定的通知函数
异步 io 的一般流程:
22(1)、调用 open 函数来打开指定的文件获得文件描数字,然后使得文件位置指向文件开始并选择
合适的标志
(2)、创建并且填充异步 io 控制块 aiocb
(3)、如果使用信号,建立信号句柄来捕获异步 io 操作完成信号
(4)、调用 aio_read,aio_write,lio_listio 请求异步 io 操作,或者调用 aio_sync 使得
异步 io 与磁盘同步
(5)、如果应用需要等待 io 操作完成,调用 aio_suspend,或者继续执行调用 aio_error 来查
询操作是否完成
(6)、io 操作完成后,调用 aio_return 抽取返回值
(7)、调用 close 关闭文件。close 函数在关闭文件之前将等待所有异步 io 完成
81、存储映射 i/o
read 和 write 函数效率是很低的,可以想象更高层次的抽象的效率.....
因为对于 read 和 write,每一个 i/o 活动都需要调用一次系统调用来完成,而且,这种方式的
i/o 活动,对于有
多个进程访问的文件,每个进程的地址空间中都含义这个文件的副本,增加了内存开销。
所谓存储映射 i/o,就是为了解决这个问题的。存储映射 i/o 在内核缓冲区中最多只有一个文件的
副本。
当多个进程需要访问该文件时,这个文件的地址都将被映射到进程的地址空间。
进程可以对一个文件建立两种类型的的映射---共享的和私有的。
对于共享映射,内核使得所有对文件共享页的写操作都直接作用于该页的共享副本,并且当这一页
刷新时,将它
存回硬盘。对于私有映射,写文件的存储映射页导致复制该页的一个副本,并且针对此副本进行更
新,但是这种更新
不影响底层文件,也不会被写回到硬盘。
函数接口:
void*
mmap(void* addr,size_t len,int prot,int flags,int
filedes,off_t off):
调用这个函数的效果是:映射文件描数字 filedes 指定的文件的[off,off+len]区域到调用
进程的内存
[paddr,paddr+len]区域。
(1)、paddr:这是函数的返回值,也就是映射区在内存的开始地址
(2)、addr:应用指定的内存映射区的开始地址,如果为 0,则内核会选择一个合理的地址,
但是不会覆盖原来的映

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台