文件的管理
标C 和UC 文件操作韩俗话的比较
由程序执行结果可知,标C文件操作函数效率比UC 文件操作函数高一些,因为标C文件操作函数内部
提供了输入输出缓冲区,也就是说当数据积累到一定数量时才会访问内核,才会写入到文件中,因此效率高
使用time来获取程序的执行时间:time a.out
real //真实时间
user //用户态时间
sys //内核态时间
可以通过自定义缓冲区的方式来提高UC 文件操作函数的效率,但并不是缓冲区越大,则效率一定高;
文件描述符的工作原理
文件描述符本质上就是一个整数,可以代表一个文件,但是文件信息并不是存放在文件描述符中,而是存
+放在文件表等数据结构中,使用open函数打开文件时,会将文件的相关信息加载到文件表数据结构中,
+但是出于安全和效率等方面的考虑,文件表结构并不适合直接操作,而是该文件表结构做一个编号,使
+用编号来进行操作,该编号就是文件描述符;
在每个进程的内部都会维护一张描述符总表,当有新的文件描述符需求时,会去文件描述符总表中查找
+最小的未被使用的文件描述符返回,文件描述符虽然是int类型,但本质上是非负整数,也就是从0开始
+其中0,1,2被系统占用,分别代表标准输入,标准输出以及标准错误,因此一般从3开始使用,文件描述
+符的最大值可以到OPEN_MAX(当前系统支持到1024);
使用close关闭文件时,本质就是将文件描述符和文件表的对应关系从文件描述符从文件描述符总表中移除
+不一定会删除文件表等结构,只有当文件表没有和任何其他文件描述符对应时(也就是一个文件表可以
+同时和多个文件描符对应),才会删除文件表,close函数并不会改变文件描述符的整数值,而是让该文件
+描述符无法代表一个文件而已;
int newfd = dup (int fd);
用来复制一个现有的文件描述符,返回的newfd一定是当前可以用文件描述符中的最小数值
int newfd = dup2 (int fd, intfd2);
fd2用来指定新描述符的值,如果fd2已经打开,则先关闭,如果fd等于fd2这dup2返回fd2,而不关闭它
注意:使用dup函数复制文件描述符时,本质上就是复制文件描述符所对应的文件表地址信息
也就是让多个文件描述符号同时对应一个文件表,最终对应同一个文件
返回:成功返回新描述符,失败—— -1
文件锁功能:
当多个进程同时向同一个文件中的同一块区域写入数据时,可能会带来数据的交错和覆盖等问题,导致
+文件中的内容产生混乱,为了解决改问题,可以使用文件锁的机制;
文件锁本质就是读写锁,也就是一个把读锁和一般写锁,其中读锁是一般共享锁,允许其他进程加读锁,
+但不允许加写锁,其中写锁是一把互斥锁,不允许其他进程加读锁和写锁;
注意:当文件在被加写锁的情况下,数据还是可以写入的,也就是说锁并不能锁定对文件的读写操作,
+只能导致第二次加锁失败(两个读锁除外)
** 一般来说,在执行读操作之前尝试加读锁,在执行写操作之前尝试加写锁,如果能加锁成功则进行
** +读写操作;如果不能加锁成功,则主动放弃读写操作的执行;
#include<unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd,... /* arg */ );
参 1:文件描述符
参 2:具体的操作命令
F_SETLK
F_SETLKW
F_GETLK //实现建议锁的功能
参 3:可变长参数,是否需要取决与cmd
struct flock {
...
short l_type; 锁的类型: F_RDLCK —— 读锁 F_WRLCK —— 写锁 F_UNLCK —— 解锁
short l_whence; 锁定的起始位置:SEEK_SET, SEEK_CUR, SEEK_END
off_t l_start; 锁的偏移量
off_t l_len; 锁定的字节数
pid_t l_pid; 锁定的进程号(F_GETLK 的参数时用 ),其它时候用-1表示
...
};
返回:成功 —— 0, 失败 —— -1
功能:主要用于对指定文件描述符执行指定的操作
1、F_SETLK作为函数实参实现文件锁的功能:
当锁的类型是F_RDLCK 或 F_WRLCK 时,实现加锁的功能
当锁的类型是F_UNLCK时,是解锁功能
无论是实现加锁还是解锁的功能,具体字节范围由第三个参数描述的锁信息来决定
当其它进程给文件已经加上冲突的锁时,该函数的调用就会返回-1,并且设置errno的数值
当前区域锁情况 请求加读锁/读操作 请求加写锁/写操作
---------------------------------------------------------------
无锁 OK/OK OK/OK
读锁 OK/OK NO/NO
写锁 NO/NO NO/NO
2、使用F_SETLKW作为函数实参的用法 —— 当加锁不成功则等待
功能与F_SETLK类似,所不同的是当锁加不上时并不会返回失败,而是进入阻塞状态进行等待,直到文
件上已经存在的冲突锁被释放为止;
3、使用F_GETLK作为函数实参的用法 —— 取锁的信息
函数中的第三个参数描述的是希望放置到文件上锁的信息,当该锁能够被放置到文件上时 fcntl
函数并不会真正地放置,而是将锁的类型改为F_UNLCK,其它成员保持不变;
如果第三个参数描述的锁信息不能被放置到文件上,则fcntl函数会使用文件上已经存在的锁信息
覆盖第三个参数描述的锁信息,也就是通过第三个参数带出文件上存在的锁信息
释放锁的方式:
a.将锁的类型改为F_UNCLK,重新调用fcntl函数解锁
b.使用close函数关闭文件描述符时,所有与该描述符有关的文件锁全部自动释放;
c.当进程结束时,会自动释放所有文件锁
#include <unistd.h>
intaccess(const char *pathname, int mode);
参 1:字符串形式的路径名
参 2:具体的操作模式
F_OK — 判断文件是否存在(重点)
R_OK — 是否可读
W_OK — 是否刻写
X_OK — 是否可执行
返回:成功 —— 0 失败 —— -1
功能:主要用于监查文件是否存在,是否拥有对应的权限
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int stat ( const char *restrict pathname, struct stat * restrict buf );
int lstat ( const char * restrictpathname, struct stat * restrict buf );
int fstat ( int fd, struct stat *buf );
stat:提供文件名,获取文件有关的信息结构
lstat:当文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是符号链接引用的文件信息
fstat:获得已在描述符fd上打开文件的有关信息
struct stat{ //文件相关信息结构体
……
mode_t st_mode; //文件类型和存取的权限 eg:100664 如取出664 则( st_mode & 0777 )
off_t st_size; //文件大小
time_t st_mtime; //文件的最后一次修改时间
……
};
返回值:成功—— 0; 失败 —— -1
S_ISREG() —— 普通文件
S_ISDIR() —— 目录文件
struct stat buf;
if( S_ISDIR(buf.st_mode) )
{
是个目录
}
#include <time.h>
char *ctime(const time_t *timep);
功能:主要用于将整数类型的时间转换成字符串类型
struct tm *localtime(const time_t *timep);
功能:主要用于整数类型的时间转换为结构体指针类型
struct tm{
int tm_sec; //秒
int tm_min; //分
int tm_hour; //时
int tm_mday; //日
int tm_mon; //月 + 1
int tm_year; //年 + 1900
……
};
获取一个文件的大小方式有3种,如:
a、使用fseek函数调整到末尾,使用ftell函数获取大小
b、使用lseek函数调整到末尾,此时返回值就是文件的大小
c、使用stat/fstat函数获取,成员st_size是文件大小
chmod / fchmod函数
#include <sys/stat.h>
intchmod(const char *path, mode_t mode);
intfchmod(int fd, mode_t mode);
参 1:字符串类型路径名或描述符
参 2:最新的权限信息
返回:成功 0 失败-1
功能:主要用于参数指定的文件权限修改为参数指定的最新权限;
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_tlength);
int ftruncate(int fd, off_t length);
参 1:字符串类型路径名或描述符
参 2:最新的长度信息
返回:成功 0 失败-1
功能:修改指定文件的长度/大小
注意:如果文件变小了,则文件中多余的数据就会丢失,如果文件变大了,则
+多出来的空间读取到的就是'\0'
#include <sys/mman.h>
void * addr=mmap(void*addr,size_t length,int prot,int flags,int fd,off_t offset);
参 1:映射的起始地址 实参给NULL表示由系统内核来指定起始位置
参 2:映射的大小
参 3:映射的访问权限,不能和文件打开模式冲突
PROT_EXEC —— 可执行
PROT_READ —— 可读
PROT_WRITE—— 可写
PROT_NONE —— 不可访问
参 4:映射的操作标志
MAP_SHARED —— 共享的,映射区数据直接反应到文件
MAP_PRIVATE —— 似有的,映射区数据不会反应到文件
参 5:文件描述符
参 6:文件的偏移量
返回:成功返回映射的起始地址,失败返回MAP_FAILED,本质就是(void *)-1;
功能:主要用于建立文件/设备到虚拟地址的映射;
int munmap(void *addr, size_tlength);
参 1:映射的其实地址,mmap函数的返回值
参 2:映射的长度
功能:主要用于解除文件/设备到虚拟地址的映色
注意:通过建立文件到虚拟地址的映射,可以将对文件的都写操作转换为对内存的操作,因此
+又多了一种都写文件内容的方式
//使用mmap函数建立文件到虚拟地址的映射
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <fcntl.h>
9 #include <sys/mman.h>
10 //定义员工的数据类型
11 typedef struct{
12 int id; //员工的编号
13 char name[20]; //员工的姓名
14 double salary; //员工的薪水
15 }Emp;
16
17 int main(void)
18 {
19
20 //1.创建文件emp.dat。使用open函数
21 int fd = open("emp.dat", O_RDWR | O_CREAT | O_TRUNC, 0664);
22 if(-1 == fd)
23 {
24 perror("open");
25 exit(-1);
26 }
27
28 printf("清空/创建文件成功\n");
29
30 //2.调整文件的大小为3个员工信息的大小
31
32 int res = ftruncate(fd, 3*sizeof(Emp));
33 if(-1 == res)
34 {
35 perror("ftruncate");
36 exit(-1);
37 }
40 printf("调整文件大小成功!\n");
41 //3.建立文件到虚拟地址的映射,mmap函数
42 void * pv = mmap(NULL, //由系统内核指定
43 3*sizeof(Emp), //映射的大小
44 PROT_READ|PROT_WRITE,//映射的权限
45 MAP_SHARED, //共享的
46 fd, //文件描述符
47 0 //文件中的偏移量
48 );
49
50 if( MAP_FAILED == pv )
51 {
52 perror("mmap");
53 exit(-1);
54 }
55
56 printf("建立文件到虚拟地址映射成功\n");
57
58 //4.通过虚拟地址存放3个员工信息
59 Emp * pe = pv;
60
69 pe[0].id = 1003;
70 strcpy(pe[0].name, "刘备");
71 pe[0].salary = 4000;
72 //5.解除文件到虚拟地址的映射,munmap函数
73
74 res = munmap(pv, 3*sizeof(Emp));
75 if(-1 == res)
76 {
77 perror("munmap");
78 exit(-1);
79 }
80
81 printf("解除文件到虚拟地址映射成功\n");
82 //6.关闭文件,使用close函数
83
84 res = close(fd);
85
86 if(-1 == res )
87 {
88 perror("close");
89 exit(-1);
90 }
91
92 perror("成功关闭文件!\n");
93 return 0;
94 }
umask() —— 主要用于设置文件在创建时需要屏蔽的权限
link() —— 主要用于创建硬链接
unlink() —— 主要用用于删除硬链接
rename() —— 主要用于重命名
remove() —— 主要用于删除文件
文件操作
目录操作 ( DIR * —— 指向存放目录相关信息的存储区指针 )
#include <dirent.h>
DIR * dp = opendir(constchar * pathname);
—— 主要用于打开参数指定的目录,打开成功返回个目录指针,失败时返回NULL;
struct dirent *readdir(DIR * dp);
—— 用于读取参数指定目录中内容,返回值,成功返回一个结构体指针,失败返回NULL
int closedir(DIR * dp);
struct dirent{
signed chard_type; //文件类型(4 为目录)
char d_name; //文件名
…… ……
};
void rewinddir(DIR * dp);
long telldir(DIR * dp);
void seekdir(DIR * dp, longloc);
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode); —— 创建空目录
#include <unistd.h>
int rmdir(const char *pathname); —— 删除一个空目录
int chadir(const char *pathname); —— 指定新的当前工作目录
char * getcwd(char * buf,size_t size);
—— buf存放获取路径的缓冲,size为buf大小,成功返回buf,失败返回NULL
#include <stdio.h>
int rename(const char *oldname, const char * newname); —— 文件或目录重命名
进程的管理
基本命令
ps —— 查看当前终端所启动的进程,是进程的瞬间状态
ps -aux 表示察看当前系统所有包含其他使用者的进程
ps -ef (UNIX) 表示以全格式的方式显示所有信息
其中进程的状态有
S 休眠状态,为减轻cpu的压力
s 进程的领导者,也就是拥有子进程
Z 僵尸进程,也就是已经结束但资源没有回收的进程
R 正在运行的进程
O 可以运行的进程
T 挂起的进程
< 优先级比较高的进程
N 优先级比较低的进程
目前主流的操作系统都支持多进程,如果进程A启动了进程B,那么进程A就叫做进程B的父进程进程B就叫做进程A的子
进程0(系统进程)负责启动进程1(init)和进程2,其他进程都是由进程1/进程2直接或间接启动起来,从而构成树形结构;
进程号是操作系统中区分不同进程的唯一标示,虽然是int类型,但是从0开始使用,采用延迟重用的策略进行管理,从而保证在任意时刻进程号都是唯一的
kill -9 进程号 表示杀死指定的进程
#include <sys/types.h>
#include <unistd.h>
getpid() - 主要用户获取当前进程的编号
getppid() - 主要用于获取当前父进程的编号
getuid() - 主要用于获取当前用户的编号
getgit() - 主要用于获取当前用户组的编号
注意: getpid()/getppid() 返回值都是pid_t类型 —— int类型
getuid() 返回值都是uid_t类型 —— unsigned int %u
getgid() 返回值都是gid_t类型 —— unsigned int %u
以上函数都没有出错的情况
进程的创建
#include <unistd.h>
pid_t fork(void);
返回:成功调用时,父进程返回子进程的进程号,子进程返回0,调用失败,父进程返回-1没有子进程被创建
注意:对于fork函数创建出来的父子进程来说,没有明确的先后执行次序,由操作的调度算法决定
父子进程的执行方式
a.对于fork函数之前的代码,由父进程执行一次;
b.对于fork函数之后的代码,由父子进程各执行一次;
c.fork函数的返回值由父子进程各返回一次;
父子进程之间的关系
a.父进程启动了子进程,父子进程同时运行,如果子进程先结束,则子进程会给父进程发信号,父进程负责回收子进程的资源
b.如果父进程先结束,子进程会变成孤儿进程,此时子进程变更父进程(一般重现设定init为新的进程)init进程应为收养了孤儿进程,因此叫做孤儿院进程
c.如果子进程先结束,但是父进程由各种原因没有收到子进程发来信号,则子进程的退出状态信息无法被回收,此时子进程就会变成僵尸进程
父子进程之间的复制关系
使用fork函数创建子进程之后,子进程会复制父进程中除了代码区之外的其他内存区域,而代码区和父进程共享(复制的是物理内存),包括缓冲区
使用fork函数创建子进程之后,子进程会复制父进程中的文件描述符总表,但不会复制文件标和v节点因此不同的文件描述符对应同一个文件表,也就同一个文件;
进程的终止
正常终止进程的方式
a.执行了main函数中的return函数
b.调用exit()函数终止进程
c.调用_exit()函数和_Exit()函数终止程序
d.最后一个线程返回;
e.最后一个线程调用了pthread_exit()函数
非正常终止进程方式
a.采用信号终止进程
b.最后一个线程被其他线程取消
#include <unistd.h>
void_exit(int status); —— UNIX_C 函数
#include<stdlib.h>
void_Exit(int status); —— 标C函数
功能:主要用于立即终止当前正在调用的进程,
a.关闭所有属于当前进程并且被打开的文件描述符;
b.让所有子进程变更父进程为init(1)进程
c.给当前进程的父进程发送SIGCHLD信号
参数值主要用于返回给当前进程父进程,作为当前进程退出状态信息,父进程若要收集该信息,则需要调用wait系列的函数;
void exit(int status);
功能:引起正常进程的终止,参数status & 0377的结果返回给父进程作为当前进程的退出状态信息,父进程获取该信息需要调用wait系列的函数;
自动调用所有由atexit()和on_exit()函数注册过的函数;
#include <stdlib.h>
int atexit(void (*function)(void));
功能:主要用于将参数指定的函数组册(保存)一下,当正常进程终止时会自动调用注册的函数,而正常进程终止方式主要有两种:一种是调用exit函数,另外一种是执行了main函数中的return语句
返回:成功返回0, 失败返回非0;
进程的等待
#include <sys/types.h>
#include<sys/wait.h>
pid_t pid = wait(int *status); <==> waitpid(-1, &status, 0);
功能:主要用于挂起当前正在调用进程的执行,直到该进程的一个子进程终止为之,包括僵尸进程;
当参数不为空时,wait函数会将子进程的退出状态信息填充到该参数所指向的int类型大小的存储空间中,为了解析该整数值,需要使用以下的参数宏
WIFEXITED( *status ) —— 如果子进程正常终止事,则返回真,正常终止有三种
a.调用exit函数
b.调用了_exit函数_Exit函数
c.执行了main函数的return;
WEXITSTATUS( *status ) —— 返回子进程的退出状态信息
返回:成功 ——终止的子进程号 失败 —— -1
pid_t waitpid(pid_t pid, int*status, int options);
参 1:进程的编号(等待那个进程结束)
< -1 表示等待任意一个进程组ID 为PID 绝对值的子进程(了解)
= -1 表示等待任意一个子进程(重点)
= 0 表示等待任意一个与当前正在调用进程在同一个进程组的子进程(了解)
> 0 表示等待进程号为PID的子进程(重点)
参 2:指针类型参数,用于带出进程的退出状态
参 3:等待(阻塞方式)方式,默认给0
返回:成功——返回状态发生改变的子进程编号 失败—— -1
功能:主要用于等待参数指定的子进程状态发生改变
#include <stdlib.h>
int on_exit (void (*function)(int , void *), void *arg);
参 1:是最后执行exit时退出的状态码
参 2:void* 就是 void *arg