系统调用说明
系统调用: 内核提供的函数,由操作系统实现并提供给外部应用程序的编程接口, 是应用程序同操作系统之间交互数据的桥梁
为了保证系统的安全性, manPage中的系统调用都是对系统调用的一次浅封装, 比如open对应的是sys_open…
系统调用和库函数的比较:预读入和缓输出
使用strace
工具可以跟踪一个程序执行时所需的系统调用。
如果规定逐字节的进行拷贝, 用库函数会比用系统调用快很多, 因为有预读入和缓输出机制:
操作系统不会让用户逐字节的向磁盘上写数据, 实际上它维护了一个系统级缓冲, 只有当从用户空间过来的数据在该缓冲上写满时, 才会一次性将数据冲刷到Disk上
当使用系统调用的方法时, 要不断的在用户空间和内核空间进行来回切换, 这会消耗大量时间
而使用fputc(库函数)时, 在设计之初自己在用户空间维护了一个缓冲, 这样在用户空间把自己的缓冲写满, 再一次性写入内核缓冲(写入了内核缓冲就认为写到了磁盘上), 可见这样大大减少了在用户空间和内核空间来回切换的次数
read和write函数常被称为UnbufferedIO, 指无用户级缓冲区, 但不保证不使用内核缓冲区
文件及相关操作
文件描述符
- PCB中有一个指针, 指向了该进程的文件描述符表, 每个表项都是一个键值对, 其中的value是指向文件结构体的指针, 其中的索引是fd, 操作系统暴露给用户的唯一操作文件的依据
- 新打开的文件描述符一定是所有文件描述符表中可用的, 最小的那个文件描述符
- 文件描述符最大1023, 说明一个进程最多能打开1024个文件
open
1 |
|
flag的参数:
- O_RDONLY
- O_WRONLY
- O_RDWR
- O_APPEND
- O_CREATE
- O_EXCL
- O_TRUNC
- O_NONBLOCK
成功返回文件描述符, 失败返回-1并设置errno;
1 | int main(int argc,char* argv[]){ |
创建文件权限时, 指定文件访问权限, 权限同时受umask影响:文件权限=mode&(~umask)
read和write
- read:从文件中读数据到缓冲区
1 |
|
成功返回实际读到的字节数, 返回0时意味着读到了文件末尾, 失败返回-1并设置errno
- wirte:从缓冲区中读数据到文件
1 |
|
成功返回实际写入的字节数, 失败返回-1, 并设置errno
read和write实现文件拷贝
1 | int main(int argc,char* argv[]){ |
阻塞和非阻塞
阻塞:当进程调用一个阻塞的系统调用时,该进程被置于睡眠状态,这时内核调度其他进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包或者调用sleep指定的睡眠时间到了)它才可能继续运行。与睡眠状态相对的是运行状态。
正在被调度执行的进程:cpu处于该进程的上下文环境中,程序计数器中保存着该进程的指令地址,通用寄存器中保存着进程运算过程中的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。
常规读文件是不会阻塞的,不论读多少字节,read一定会在有限时间内返回。从终端或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞。如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于阻塞多长时间也是不确定的,如果一直没有数据到达就会一直阻塞。同样,写常规文件也是不会阻塞的,而向终端设备或网络写则不一定。
echo程序
1 | int main(int argc,char* argv[]){ |
当不敲入换行符时, read会一直阻塞等待用户输入
阻塞是设备文件, 网络文件的属性
非阻塞方式从tty中读数据
1 | int main(int argc,char* argv[]){ |
当read
函数返回-1, 并且errno=EAGAIN或EWOULDBLOCK
, 说明不是read
失败, 而是read在以非阻塞方式读一个设备文件或网络文件, 而文件中无数据。
阻塞方式存在的问题也正是网络IO中select, poll和epoll
函数存在的原因。
fcntl修改文件的属性
改变一个已经打开的文件的访问控制属性。
1 |
|
用fcntl改写上面的程序, 不用重新打开文件:
1 | int main(int argc,char* argv[]){ |
文件的flags是一个位图, 每一位代表不同属性的真假值
lseek函数
文件偏移:每个打开的文件都记录着当前读写位置,打开文件时候写位置是0,表示文件开头,通常读写多少个字节就会将读写位置往后移动多少个字节。例外:如果以O_APPEND
方式打开,每次写操作都会在文件末尾追加数据,然后将读写位置移动到新的文件末尾。lseek
和标准I/O
库的fseek
函数类似,可以移动当前读写位置(或偏移量)。
1 |
|
例子:
1 | int main(int argc,char* argv[]){ |
用lseek获取文件大小:
1 | int main(int argc,char* argv[]){ |
使用lseek拓展文件大小: 要想使文件大小真正拓展, 必须引起IO操作
1 | int main(int argc,char* argv[]){ |
以HEX查看文件:od -tcx filename
。
也可以使用truncate拓展文件大小:
1 | int ret=truncate("dict.cp",250); |
目录操作
目录项和inode
文件存储的关键点:inode, denty,数据存储,文件系统。
inode: 本质为结构体,存储文件的属性信息。如:权限、大小、时间、用户、盘块位置…也叫做文件属性管理结构,大多数的inode都存储在磁盘上。
可以用stat命令,查看某个文件的inode信息:stat example.txt
inode的大小:
inode会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。
查看每个硬盘分区的inode总数和已经使用的数量,可以使用df命令:
df -i
denty: 目录项,本质依然是结构体,重要成员变量:文件名,inode。文件内容(data)保存在磁盘块中。
增加文件的硬链接只是增加dentry, 指向相同的inode
同样, 删除硬链接也只是删除dentry, 要注意删除文件并不会让数据在磁盘消失, 只是OS丢失了inode, 磁盘只能覆盖, 不能擦除。
stat函数
stat函数作用:获取文件属性(从inode中获取)
1 |
|
利用stat获取文件大小:
1 | int main(int argc,char* argv[]){ |
使用宏函数获取文件属性:
1 | int main(int argc,char* argv[]){ |
lstat
ln -s makefile makefile.soft
:创建软连接
mkfifo f1
:创建管道文件
stat穿透: 当用stat获取软连接的文件属性时, 会穿透符号连接直接返回软连接指向的本尊的文件属性(vim,cat命令也有穿透作用)
解决方法: 换lstat函数
S_IFMT是一个文件类型掩码(文件类型那四位全1), st_mode与它位与后就可以提取出文件类型(后面的权限位被归零)
link和unlink
特殊权限位:包含三个二进制位。依次是:设置组ID位:setGID; 设置用户ID位setID; 黏住位sticky
黏住位: 早期计算机内存紧张,只有精要的常用的程序可以常驻物理内存,剩下的要暂存在磁盘中。**当内存不够的时候会将该部分程序存回磁盘,腾出内存空间。若文件设置了黏住位,即使在内存比较吃紧的情况下也不会将该文件回存到磁盘上。**现阶段操作系统的虚拟内存管理分页算法比较完善,该功能已经被废弃。
仍然可以对目录设置黏住位。被设置了该位的目录,其内部文件只有:
- 超级管理员
- 该目录所有者
- 改文件的所有者
这三种用户有权限做删除、修改操作。其他用户可以读、创建,但不能随意删除。
link函数
可以为已经存在的文件创建目录项(硬链接)
ln makefile makefile.hard
:为makefile创建硬连接
1 | int link(const char *oldpath, const char *newpath); |
使用link和unlink函数实现mv命令:
1 | int main(int argc,char* argv[]){ |
Linux下的文件删除机制: 不断的将文件的st_nlink-1, 直到减到0为止. 无目录项对应的文件, 会被操作系统择机释放。因此删除文件, 从某种意义上来说只是让文件具备了被删除的条件
unlink函数的特征:清除文件时, 如果文件的硬连接计数减到了0, 没有dentry与之对应, 但该文件仍不会马上被释放掉. 要等到所有打开该文件的进程关闭该文件, 系统才会择机将文件释放。
1 | int main(int argc,char* argv[]){ |
如果在unlink之前诱发段错误, 程序崩溃, temp.txt就会存活下来. 所以将unlink这一步放到打开文件之后紧接着就unlink掉
虽然文件被unlink掉了, 用户用cat查看不到磁盘上的对应文件, 但是write函数拿到fd写文件是向内核的buffer中写, 仍可正常写入
隐式回收:
当进程运行结束时, 所有该进程打开的文件会被关闭, 申请的内存空间会被释放, 系统的这一特性称为隐式回收系统资源
文件目录权限
readlink m1.soft
:查看软连接的内容
Linux下所见皆文件, 如果用vim打开一个目录,目录也是”文件”。文件内容是该目录下所有子文件的目录项dentry。
r | w | x | |
---|---|---|---|
文件 | 文件内容可以被查看,cat、more、less… | 内容可以被修改vi… | 运行产生一个进程 ./文件名 |
目录 | 目录可以被浏览 | 创建、删除、修改文件mv, touch, mkdir… | 可以被打开、进入 cd |
目录操作函数
文件名不能超过255个字符, 引文dirent中的d_name长度为256, 再算上\0, 有255个字符可用。
1 |
|
用目录操作函数实现ls的功能:
1 | int main(int argc,char* argv[]){ |
递归遍历目录
思路:
判断命令行参数, 获取用户要查询的目录名-argv[1]
- 注意如果argc==1, 说明要查询的是当前目录./
判断用户指定的是否是目录: stat S_ISDIR()->封装函数isFile()
- 读目录:
1 | opendir(dir); |
实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52/*参2是回调函数名*/
void fetchdir(const char* dir,void(*fcn)(char*)){
char name[PATH_LEN];
struct dirent* sdp;
DIR* dp;
/*打开目录失败*/
if((dp=opendir(dir))==NULL){
fprintf(stderr,"fetchdir:can't open %s\n",dir);
return;
}
/*循环读取内容*/
while((sdp=readdir(dp))!=NULL){
/*遇到当前目录和上一级目录,跳过,否则会陷入死循环*/
if((strcmp(sdp->d_name,".")==0)||(strcmp(sdp->d_name,"..")==0))
continue;
/*路径名是否越界*/
if(strlen(dir)+strlen(sdp->d_name)+2>sizeof(name)){
fprintf(stderr,"fetchdir:name %s %s is too long\n",dir,sdp->d_name);
}else{
/*拼接为一个路径,传给isFile函数*/
sprintf(name,"%s/%s",dir,sdp->d_name);
(*fcn)(name);
}
}
closedir(dp);
}
void isFile(char* name){
struct stat sbuf;
/*获取文件属性失败*/
if(stat(name,&sbuf)==-1){
fprintf(stderr,"isFile:can't access %s\n",name);
exit(1);
}
/*这是一个目录文件:调用函数fetchdir*/
if((sbuf.st_mode&S_IFMT)==S_IFDIR){
fetchdir(name,isFile);
}
/*不是目录文件:是一个普通文件,打印文件信息*/
printf("%ld\t\t%s\n",sbuf.st_size,name);
}
int main(int argc,char* argv[]){
/*不指定命令行参数*/
if(argc==1)
isFile(".");
else{
while(--argc>0)
isFile(*++argv);
}
return 0;
}
dup和dup2
cat makefile > m1
:将cat的结果重定向到m1(此时m1与makefile内容相同)
cat makefile >> m1
:将cat的结果重定向并追加到m1后面(此时m1是双份的makefile)
1 |
|
The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.
传入已有的文件描述符, 返回一个新的文件描述符:
1 |
|
dup的返回值fd2相当于fd1的副本, 拿着它也可以操作fd1
dup2:后面的指向前面的。
1 |
|
fcntl实现dup描述符
1 | int main(int argc,char* argv[]){ |