系统调用说明 系统调用: 内核提供的函数 ,由操作系统实现并提供给外部应用程序的编程接口, 是应用程序同操作系统之间交互数据的桥梁
为了保证系统的安全性, 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 table),存放inode所包含的信息。 
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 ; }