系统调用说明 系统调用: 内核提供的函数 ,由操作系统实现并提供给外部应用程序的编程接口, 是应用程序同操作系统之间交互数据的桥梁
为了保证系统的安全性, manPage中的系统调用都是对系统调用的一次浅封装 , 比如open对应的是sys_open…
系统调用和库函数的比较:预读入和缓输出 使用strace
工具可以跟踪一个程序执行时所需的系统调用 。
如果规定逐字节的进行拷贝, 用库函数会比用系统调用快很多, 因为有预读入和缓输出机制:
操作系统不会让用户逐字节的向磁盘上写数据, 实际上它维护了一个系统级缓冲 , 只有当从用户空间过来的数据在该缓冲上写满时, 才会一次性将数据冲刷到Disk上
当使用系统调用的方法时, 要不断的在用户空间和内核空间进行来回切换, 这会消耗大量时间
而使用fputc(库函数)时, 在设计之初自己在用户空间维护了一个缓冲, 这样在用户空间把自己的缓冲写满, 再一次性写入内核缓冲(写入了内核缓冲就认为写到了磁盘上), 可见这样大大减少了在用户空间和内核空间来回切换的次数
read和write函数常被称为UnbufferedIO, 指无用户级缓冲区, 但不保证不使用内核缓冲区
文件及相关操作 文件描述符
PCB中有一个指针, 指向了该进程的文件描述符表, 每个表项都是一个键值对, 其中的value是指向文件结构体的指针, 其中的索引是fd, 操作系统暴露给用户的唯一操作文件的依据
新打开的文件描述符一定是所有文件描述符表中可用的, 最小的 那个文件描述符
文件描述符最大1023, 说明一个进程最多能打开1024个文件
open 1 2 3 4 5 6 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open (const char * pathname, int flags) ;int open (const char * pathname, int flags, mode_t mode) ;
flag的参数:
O_RDONLY
O_WRONLY
O_RDWR
O_APPEND
O_CREATE
O_EXCL
O_TRUNC
O_NONBLOCK
成功返回文件描述符 , 失败返回-1并设置errno;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main (int argc,char * argv[]) { int fd1=0 ; int fd2=0 ; fd1=open ("./dirt.txt" ,O_RDONLY|O_CREAT|O_TRUNC,0644 ); fd2=open ("./dirt2.txt" ,O_RDONLY); printf ("fd1=%d\n" ,fd1); printf ("fd2=%d,errno=%d:%s\n" ,fd2,errno,strerror(errno)); close (fd1); close (fd2); return 0 ; }
创建文件权限时, 指定文件访问权限, 权限同时受umask影响:文件权限=mode&(~umask)
read和write
1 2 #include <unistd.h> ssize_t read (int fd, void * buf, size_t count);
成功返回实际读到的字节数, 返回0时意味着读到了文件末尾, 失败返回-1并设置errno
1 2 #include <unistd.h> ssize_t write (int fd, const void * buf, size_t count);
成功返回实际写入的字节数, 失败返回-1, 并设置errno
read和write实现文件拷贝 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 int main (int argc,char * argv[]) { char buf[1024 ]; int n=0 ; int fd1=open (argv[1 ],O_RDONLY); if (fd1==-1 ){ perror("open argv1 error" ); exit (1 ); } int fd2=open (argv[2 ],O_RDWR|O_CREAT|O_TRUNC,0644 ); if (fd2==-1 ){ perror("open argv2 error" ); exit (1 ); } while ((n=read (fd1,buf,sizeof (buf)))!=0 ){ if (n<0 ){ perror("open argv2 error" ); break ; } write (fd2,buf,n); } close (fd1); close (fd2); return 0 ;
阻塞和非阻塞 阻塞:当进程调用一个阻塞的系统调用时,该进程被置于睡眠状态,这时内核调度其他进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包或者调用sleep指定的睡眠时间到了)它才可能继续运行 。与睡眠状态相对的是运行状态。
正在被调度执行的进程:cpu处于该进程的上下文环境中,程序计数器中保存着该进程的指令地址,通用寄存器中保存着进程运算过程中的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。
常规读文件是不会阻塞的,不论读多少字节,read一定会在有限时间内返回。从终端或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞 。如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于阻塞多长时间也是不确定的,如果一直没有数据到达就会一直阻塞。同样,写常规文件也是不会阻塞的,而向终端设备或网络写则不一定。
echo程序 1 2 3 4 5 6 7 8 9 10 11 12 13 int main (int argc,char * argv[]) { int n=0 ; char buf[10 ]; n=read (STDIN_FILENO,buf,sizeof (buf)); if (n==-1 ){ perror("read error" ); exit (1 ); } write (STDOUT_FILENO,buf,n); return 0 ; }
当不敲入换行符时, read会一直阻塞等待用户输入
阻塞是设备文件, 网络文件 的属性
非阻塞方式从tty中读数据 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 int main (int argc,char * argv[]) { int fd=0 ; char buf[10 ]; int n=0 ; fd=open ("/dev/tty" ,O_RDONLY|O_NONBLOCK); if (fd<0 ){ perror("open /dev/tty error" ); exit (1 ); } tryagain: n=read (fd,buf,sizeof (buf)); if (n<0 ){ if (errno!=EWOULDBLOCK){ perror("read /dev/tty error" ); exit (1 ); }else { write (STDOUT_FILENO,"try again\n" ,strlen ("try again\n" )); sleep(2 ); goto tryagain; } } write (STDOUT_FILENO,buf,n); close (fd); return 0 ;
当read
函数返回-1, 并且errno=EAGAIN或EWOULDBLOCK
, 说明不是read
失败, 而是read在以非阻塞方式读一个设备文件 或网络文件 , 而文件中无数据。
阻塞方式存在的问题也正是网络IO中select, poll和epoll
函数存在的原因。
fcntl修改文件的属性 改变一个已经打开的 文件的访问控制属性。
1 2 3 #include <unistd.h> #include <fcntl.h> int fcntl (int fd, int cmd, ... ) ;
用fcntl改写上面的程序, 不用重新打开文件:
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 int main (int argc,char * argv[]) { int fd=0 ; char buf[10 ]; int n=0 ; int ret=0 ; int flags=0 ; flags=fcntl(STDIN_FILENO,F_GETFL); if (flags==-1 ){ perror("fcntl error" ); exit (1 ); } flags|=O_NONBLOCK; ret=fcntl(STDIN_FILENO,F_SETFL,flags); if (ret==-1 ){ perror("fcntl error" ); exit (1 ); } tryagain: n=read (STDIN_FILENO,buf,sizeof (buf)); if (n<0 ){ if (errno!=EWOULDBLOCK){ perror("read /dev/tty error" ); exit (1 ); }else { write (STDOUT_FILENO,"try again\n" ,strlen ("try again\n" )); sleep(2 ); goto tryagain; } } write (STDOUT_FILENO,buf,n); close (fd); return 0 ; }
文件的flags是一个位图, 每一位代表不同属性的真假值
lseek函数 文件偏移:每个打开的文件都记录着当前读写位置,打开文件时候写位置是0,表示文件开头,通常读写多少个字节就会将读写位置往后移动多少个字节。例外:如果以O_APPEND
方式打开,每次写操作都会在文件末尾追加数据,然后将读写位置移动到新的文件末尾 。lseek
和标准I/O
库的fseek
函数类似,可以移动当前读写位置(或偏移量)。
1 2 3 4 5 6 7 #include <sys/types.h> #include <unistd.h> int fseek (FILE *stream, long offset, int whence) ; off_t lseek(int fd, off_t offset, int whence);
例子:
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 int main (int argc,char * argv[]) { int fd=0 ; int n=0 ; char msg[]="It's a lseek test\n" ; char c; fd=open ("./lseek.txt" ,O_CREAT|O_RDWR,0644 ); if (fd==-1 ){ perror("open error" ); exit (1 ); } write (fd,msg,strlen (msg)); lseek(fd,0 ,SEEK_SET); while ((n=read (fd,&c,1 ))){ if (n==-1 ){ perror("read error" ); exit (1 ); } write (STDOUT_FILENO,&c,n); } close (fd); return 0 ; }
用lseek获取文件大小:
1 2 3 4 5 6 7 8 9 10 11 12 13 int main (int argc,char * argv[]) { int fd=open (argv[1 ],O_RDWR); if (fd==-1 ){ perror("open error" ); exit (1 ); } int size =lseek(fd,0 ,SEEK_END); printf ("The file's size:%d\n" ,size ); close (fd); return 0 ; }
使用lseek拓展文件大小: 要想使文件大小真正拓展, 必须引起IO操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main (int argc,char * argv[]) { int fd=open (argv[1 ],O_RDWR); if (fd==-1 ){ perror("open error" ); exit (1 ); } int size =lseek(fd,110 ,SEEK_END); printf ("The file's size:%d\n" ,size ); write (fd,"\0" ,1 ); close (fd); return 0 ; }
以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 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 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat (const char * pathname, struct stat* statbuf) ;struct stat { dev_t st_dev; ino_t st_ino; mode_t st_mode; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size; blksize_t st_blksize; blkcnt_t st_blocks; struct timespec st_atim ; struct timespec st_mtim ; struct timespec st_ctim ; #define st_atime st_atim.tv_sec #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec };
利用stat获取文件大小:
1 2 3 4 5 6 7 8 9 10 11 int main (int argc,char * argv[]) { struct stat sbuf ; int ret=0 ; ret=stat(argv[1 ],&sbuf); if (ret==-1 ){ perror("stat error" ); exit (1 ); } printf ("file size:%ld\n" ,sbuf.st_size); return 0 ; }
使用宏函数获取文件属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main (int argc,char * argv[]) { struct stat sbuf ; int ret=0 ; ret=stat(argv[1 ],&sbuf); if (ret==-1 ){ perror("stat error" ); exit (1 ); } if (S_ISREG(sbuf.st_mode)) printf ("It's a regular\n" ); else if (S_ISDIR(sbuf.st_mode)) printf ("It's a dir\n" ); else if (S_ISFIFO(sbuf.st_mode)) printf ("It's a pipe\n" ); else if (S_ISLNK(sbuf.st_mode)) printf ("It's a symbol" ); return 0 ; }
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main (int argc,char * argv[]) { int ret=0 ; ret=link(argv[1 ],argv[2 ]); if (ret==-1 ){ perror("link error" ); exit (1 ); } ret=unlink(argv[1 ]); if (ret==-1 ){ perror("unlink error" ); exit (1 ); } return 0 ; }
Linux下的文件删除机制: 不断的将文件的st_nlink-1, 直到减到0为止. 无目录项对应的文件, 会被操作系统择机释放 。因此删除文件, 从某种意义上来说只是让文件具备了被删除的条件
unlink函数的特征:清除文件时, 如果文件的硬连接计数减到了0, 没有dentry与之对应, 但该文件仍不会马上被释放掉. 要等到所有打开该文件的进程关闭该文件, 系统才会择机将文件释放。
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 int main (int argc,char * argv[]) { int fd=0 ; int ret=0 ; char * p="test of unlink\n" ; char * p2="after write something\n" ; fd=open ("temp.txt" ,O_RDWR|O_TRUNC|O_CREAT,0644 ); if (fd<0 ) perr_exit("open file error" ); ret=write (fd,p,strlen (p)); if (ret==-1 ) perr_exit("write error" ); printf ("hello,I'm printf\n" ); ret=write (fd,p2,strlen (p2)); if (ret==-1 ) perr_exit("write error" ); printf ("Entry key to continue\n" ); getchar(); close (fd); ret=unlink("temp.txt" ); if (ret==-1 ) perr_exit("unlink error" ); return 0 ; }
如果在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 2 3 4 5 6 7 8 9 10 11 12 #include <dirent.h> DIR* opendir (const char * name) ; int closedir (DIR* dirp) ;struct dirent* readdir (DIR* dirp) ;struct dirent { ino_t d_ino; off_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[256 ]; };
用目录操作函数实现ls的功能:
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 int main (int argc,char * argv[]) { DIR* dp; struct dirent * sdp ; dp=opendir(argv[1 ]); if (dp==NULL ) perr_exit("opendir error" ); while ((sdp=readdir(dp))!=NULL ){ if (!strcmp (sdp->d_name,"." )) continue ; if (!strcmp (sdp->d_name,".." )) continue ; printf ("%s\n" ,sdp->d_name); } printf ("\n" ); closedir(dp); return 0 ; }
递归遍历目录 思路:
1 2 3 4 5 6 7 opendir(dir); while (readdir()){ 普通文件:直接打印; 目录文件:拼接目录访问绝对路径:sprintf (path,"%s%s" ,dir,d_name); 递归调用自己:opendir(path), readdir, closedir; } closedir();
实现: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 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 { 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 ); } 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 2 3 #include <unistd.h> int dup (int oldfd) ;int dup2 (int oldfd, int newfd) ;
The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.
传入已有的文件描述符, 返回一个新的文件描述符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main (int argc,char * argv[]) { int fd1=open (argv[1 ],O_RDWR|O_CREAT|O_TRUNC,0644 ); if (fd1==-1 ){ perror("open error" ); exit (1 ); } int fd2=dup(fd1); if (fd2==-1 ){ perror("dup error" ); exit (1 ); } printf ("fd1=%d fd2=%d\n" ,fd1,fd2); write (fd2,"love you\n" ,8 ); return 0 ; }
dup的返回值fd2相当于fd1的副本, 拿着它也可以操作fd1
dup2:后面的指向前面的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> int main (int argc,char * argv[]) { int fd1=open (argv[1 ],O_RDWR|O_CREAT|O_TRUNC,0644 ); int fd2=open (argv[2 ],O_RDWR|O_CREAT|O_TRUNC,0644 ); int fdret=dup2(fd1,fd2); printf ("fdret=%d\n" ,fdret); int ret=write (fd2,"love you\n" ,9 ); printf ("ret=%d\n" ,ret); dup2(fd1,STDOUT_FILENO); printf ("--------love you--------\n" ); return 0 ; }
fcntl实现dup描述符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main (int argc,char * argv[]) { int fd1=open (argv[1 ],O_RDWR|O_CREAT,0644 ); printf ("fd1=%d\n" ,fd1); int newfd1=fcntl(fd1,F_DUPFD,0 ); printf ("newfd1=%d\n" ,newfd1); int newfd2=fcntl(fd1,F_DUPFD,8 ); printf ("newfd2=%d\n" ,newfd2); int ret=write (newfd2,"fuckyou\n" ,8 ); printf ("ret=%d\n" ,ret); return 0 ; }