PS:推荐最近刚撸完的一本书<linux程序设计>,此书着重讲述了关于linux下程序设计的一些简单设计,但是不够深入,拿来入门还是非常不错的,如果apue读着有些困难,那么此书是一个不错的选择。再分享一个提问须知:https://zhuanlan.zhihu.com/p/20752519
Apue中文第三版下载地址:http://download.csdn.net/download/xiaoyu5256/9803116
废话少说,开始第三章。我还是直接拿实例程序直接干,书上的原理性问题我会用通俗的语言来描述,主要是为了让新手更好的理解。
文件i/o,顾名思义,in and out,举个例子,当你打开一个文件的时候,就和你打开一本书是一个道理,是看书,还是去写,是你来决定的。再说几个关于本章的几个关键性词语,以下是对原理的阐述。
三个幻数0:in(STDIN_FILENO) 1:out(STDOUT_FILENO) 2:error(STDERR_FILENO)(一会用到了就明白了)
文件描述符:非负数,通俗来讲就是当你打开一个文件的时候,计算机会找一个数字来表示你当前打开的文件流,最大是63.
偏移量:你可以把文件看成一条定长的公路,这个长度也就是是文件所占的磁盘块数大小,你如果想使公路增加长度,那么你需要阔路,但这并不是真实的文件大小,只是表面的,路还是那么长。你想扩展的大小就是所谓的偏移量。
相对路径和绝对路径:相对路径就是相对于当前文件所在目录的文件所在位置,绝对路径就是相对于根目录(/)的文件所在位置。
文件空洞:一会用代码说明,看下这篇博客也可以(http://blog.csdn.net/clamercoder/article/details/38361815)(空洞文件作用很大,例如迅雷下载文件,在未下载完成时就已经占据了全部文件大小的空间,这时候就是空洞文件。下载时如果没有空洞文件,多线程下载时文件就都只能从一个地方写入,这就不是多线程了。如果有了空洞文件,可以从不同的地址写入,就完成了多线程的优势任务。)
缓冲区:缓冲区大致分为用户缓冲区和内核缓冲区,都是在内存当中,用户缓冲区是用户可以直接操作的。这两者之间的数据频繁交换非常的占用系统资源
原子操作:比方说小明喜欢上了一个女神,他想让女神直到自己的爱慕,此时假设他有两种选择,第一种是让女神的室友吹枕边风,那么他需要先告诉女神室友自己的爱慕之情,之后女神室友去旁敲侧击。第二种是直接向女神告白,成不成在此一搏。两者都可以完成小明的倾诉,但是假设女神室友还没有旁敲侧击的时候,隔壁老王趁机向女神告白把女神拿下了,小明就尴尬了2333,我个人偏向第二种,第二种便是原子操作,但是也需要看什么样的情况再做决断。
用户态和内核态:一般的程序或者说是进程是运行在用户态的,他们需要向网络发送数据的时候,数据会从用户缓冲区到内核缓冲区再发送到网络上。举个简单的例子,线程池:当有一个用户连接过来的时候,此时再去创建线程进行服务是需要用户态和内核态之间切换的,频繁的切换占用大量cpu时间片。所以我先创建很多线程等待用户的连接,节省了两者的切换,提高了性能。有点扯远了,大致就是这样。
| & ^:这三个位操作符,自己查一下吧。(我手边的书上就有:c++primer第六版的137页)
文件访问权限:随便ls -l一个文件看看,文件所有者权限,用户组权限,其他用户权限,看一下鸟哥的书吧,上边写的很详细,隐藏文件权限和umask也着重去看以下,将来也会用到。
书中在open和openat函数后写了很多关于标志位的解释,在后面的程序都会有用到,所以可以先跳过,用到的时候可以再回来查询。
虽然很枯燥,但是还是需要继续看代码。
#include <sys/types.h> #include <unistd.h> #include<stdio.h> #include<stdlib.h> int main() { if(lseek(STDIN_FILENO, 0, SEEK_CUR) == -1) printf("cannot seek\n"); else { printf("seek OK\n"); } exit(0); }
1. SEEK_SET,文件偏移量将被设置为 offset。
2. SEEK_CUR,文件偏移量将被设置为当前值加上 offset,offset可以为正也可以为负。
3. SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset可以为正也可以为负。
此程序是测试文件是否可以被设置偏移量,代码很简单,不再多说。
#include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #define FILE_MODE 0644 char buf1[] = "abcdefghij"; char buf2[] = "ABCDEFGHIJ"; int main() { int fd; if((fd = creat("file.hole", FILE_MODE)) < 0) perror("create error\n"); if(write(fd, buf1, 10) != 10) perror("buf1 write error"); if((lseek(fd, 16384, SEEK_SET)) == 10) perror("lseek error"); if(write(fd, buf2, 10) != 10) perror("buf2 write error"); exit(0); }
这个程序是创建一个具有空洞的文件,使用ls查看该文件大小你会发现它和没有空洞的文件是一样的,但是所占的磁盘块(block)不一样,具有空洞的文件占的很少,也就是说,其表面大小和物理大小是完全不同的,见博客http://blog.csdn.net/clamercoder/article/details/38361815
关于多个文件描述符打开同一个文件的说明:
由此图可以看出当前文件偏移量和当前文件长度不是一个东西,偏移量大于文件长度就造成了文件的空洞,而前面所说的文件描述符也就是上图中进程维护的一张表。i节点中的文件长度表示的是实际所占的磁盘块数量。值得注意的是lseek只改变文件偏移量大小,并不进行任何IO操作。
Dup函数的功能就是如上图所示,书上讲述的也很明白,不再赘述。
原子操作上文也用了通俗的语言去解释,书上举得例子是lseek之后去read,也就是先去更改偏移量,然后再去写,这里面实际上是牵涉了内核用户态切换的内容。直接pwrite是一个原子操作,也就是直接陷入内核态去执行写操作。
Fcntl函数在后边的网络编程中经常用到,用来去设置一个非阻塞socket。
#include<stdio.h> #include<stdlib.h> #include<fcntl.h> int main(int argc, char *argv[]) { int val; if(argc != 2) perror("usage: fcntl_2 <description#>"); if(val = fcntl(atoi(argv[1]), F_GETFL, 0) < 0) printf("fcntl error for fd %d", atoi(argv[1])); switch(val & O_ACCMODE){ case O_RDONLY: printf("read only"); break; case O_WRONLY: printf("write noly"); break; case O_RDWR: printf("read write"); break; default: perror("unknown access mode"); } if(val & O_APPEND) printf(", append"); if(val & O_NONBLOCK) printf(", nonblocking"); if(val & O_SYNC) printf(", synchronous writes"); #if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC) if(val & O_FSYNC) printf(", synchronous writes"); #endif putchar('\n'); exit(0); }程序也不难,先获取一个文件描述符的当前标志位,使用O_ACCMODE这个宏作为一个掩码与文件状态标识值做AND位运算,产生一个表示文件访问模式的值,然后判断该文件该文件访问权限。书中的set_fl函数是经常用的,请牢记在心。
关于内核和用户态切换开销的问题我本周会用netcat进行一个小的网络测试来说明这个问题。