write写二进制比较麻烦,因为写的是字节流,不涉及二进制的处理
而fwrite(,"wb");就可以写入二进制
多个进程在同时写入一个文件,会出现写冲突,对不对?
不对,得是同一个文件得同一个区域
mode_t:权限类型 size_t:计数 off_t:偏移 ssize_t:
文件锁读写冲突
1.如果两个或以上的进程同时向同一个文件的某个特定区域写入数据,那么最后写入文件的数据极有可能因为写操作的交错而产生混乱
2.如果一个进程写而其它进程同时在读一个文件的某个特定区域,那么读出的数据极有可能因为读写操作的交错而不完整
3.多个进程同时读一个文件的某个特定区域,不会有任何问题,它们只是各自文件中的数据拷贝到各自的缓冲区中,并不会改变文件的内容,相互之间也就不会冲突
4.由此可以得出结论,为了避免在读写同一个区域时发生冲突,进程之间应该遵循以下规则,如果一个进程正在写,那么其它进程既不能写也不能读,如果一个进程正在读,那么其它进程不能写但是可以读’
//写冲突演示 #include#include #include #include int main(int argc,char* argv[]){ if(argc < 2){ fprintf(stderr,"用法:./a.out <字符串>n"); return -1; } //打开文件 int fd = open("./shared.txt",O_WRONLY | O_CREAT | O_APPEND,0664); if(fd == -1){ perror("open"); return -1; } //写入数据 // ./a.out hello // argv[1]记录着hello,argv[1]记录 ./a.out for(int i = 0;i < strlen(argv[1]);i++){ if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){ perror("write"); return -1; } sleep(1); } //关闭文件 close(fd); return 0; }
文件锁
1.为了避免多个进程在读写同一个文件的同一个区域时发生冲突,Unix/Linux系统引入了文件锁机制,并把文件锁分为读锁和写锁两种,它们的区别在于,对一个文件的特定区域可以加多把读锁,对一个文件的特定区域只能加一把锁
2.基于锁的操作模型是:读/写文件中的特定区域之前,先加上读/写锁,锁成功了再读/写,读/写完成以后再解锁
3.假设进程A期望访问某文件的A区,同时进程B期望访问该文件的B区,而A区和B区存在部分重叠,分情况讨论
第一种情况:进程A正在写,进程B也想写
第二种情况:进程A正在写,进程B却想读
第三种情况:进程A正在读,进程B却想写
第四种情况:进程A正在读,进程B也想读
#include
int fcntl(int fd,F_SETLK/F_SETLKW,struct flock* lock);
功能:加解锁
参数:F_SETLK 非阻塞模式加锁,,F_SETLKW 阻塞模式加锁
lock 文件要加的锁
返回值:成功返回0,失败返回-1
非阻塞模式加锁,函数返回,然后我们可以执行其它功能,过一会再调
阻塞模式加锁,即一直停滞等到解锁再加锁
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; //加锁进程的PID,-1表示自动设置
};
通过对该结构体类型变量的赋值,再配合fcntl函数,以完成对文件指定区域的加解锁操作。
几点说明:
1.当通过close函数关闭文件描述符时,调用进程在该文件描述符上所加的一切锁将被自动解除
2.当进程终止时,该进程在所有文件描述符上所加的一切锁将被自动解除
3.文件锁仅在不同进程之间起作用,同一个进程的不同线程不能通过文件锁解决读写冲突问题。
4.通过fork/vfork函数创建的子进程,不继承父进程所加的任何文件锁
5.通过exec函数创建的新进程,会继承原进程所加的全部文件锁,除非某文件描述符带有FD_CLOEXEC标志。
从前述基于锁的操作系统可以看出,锁机制之所以能够避免读写冲突,关键在于参与读写的多个进程都在按照一套模式 ----先加锁,再读写,最后解锁 ------按部就班地执行。这就形成了一套协议,只要参与者无一例外地遵循这套协议,读写就是安全的。反之,如果哪个进程不遵守这套协议,完全无视锁地存在,想读就读,想写就写,即便有锁,对它也起不到任何约束作用。因此,这样地锁机制被称为劝谏锁或协议锁
//文件锁演示 #include#include #include #include #include //加锁函数 int wlock(int fd,int wait){ struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0;//0 一直加到文件尾 lock.l_pid = -1; return fcntl(fd,wait ? F_SETLKW : F_SETLK,&lock); } //解锁 int unlock(int fd){ struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0;//0 一直加到文件尾 lock.l_pid = -1; return fcntl(fd,F_SETLK,&lock); } int main(int argc,char* argv[]){ if(argc < 2){ fprintf(stderr,"用法:./a.out <字符串>n"); return -1; } //打开文件 int fd = open("./shared.txt",O_WRONLY | O_CREAT | O_APPEND,0664); if(fd == -1){ perror("open"); return -1; } //阻塞加写锁 //非阻塞加锁 while(wlock(fd,0) == -1){ if(errno == EACCES || errno == EAGAIN){//出错是因为文件被锁定,加锁失败 printf("文件被锁定,干点别的去...n"); sleep(1); }else{//其他原因造成的失败,正常错误处理 perror("wlock"); return -1; } } //写入数据 // ./a.out hello // argv[1] for(int i = 0;i < strlen(argv[1]);i++){ if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){ perror("write"); return -1; } sleep(1); } //解锁 if(unlock(fd) == -1){ perror("unlock"); return -1; } //关闭文件 close(fd); return 0; }
文件阻塞加写锁:
文件非阻塞写锁:
当判断while(wlock(fd,0) == -1)时,wlock其实有很多可能失败返回-1,1.加锁失败 2.环境出问题。 3参数传的不对。 都会返回 -1,那么如何判断是加锁失败导致 -1呢?
通过man手册看一下, man 2 fcntl
意思是,如果锁因为其它进程有锁而没加上的话,错误码会设置成EACCES或者EAGAIN,这也是要在头文件加一个#include 的原因
文件锁地内核结构
1.每次对给定文件地特定区域加锁,都会通过fcntl函数向系统内核传递flock结构体,该结构体包含了有关锁的一切细节,诸如锁地类型(读锁/写锁),锁区地起始位置和大小,甚至加锁进程地PID(填-1由系统自动设置),即构成锁节点
2.系统内核会收集所有进程对该文件所加地各种锁,并把这些flock结构体中地信息,以链表地形式组织成一张锁表,而锁表地起始地址就保存在该文件的v节点中
3.任何一个进程通过fcntl函数对该文件加锁,系统内核都要遍历这张锁表,一旦发现有与欲加之锁构成冲突的锁即阻塞或报错,否则即将欲加之锁插入锁表,即加一个节点,而解锁的过程实际上就是调整或删除锁表中的相应节点
访问测试
#include
int access(char const* pathname,int mode);
功能:判断当前进程是否可以对某个给定的文件执行某种访问
参数:pathname 文件路径
mode 被测试权限,可以取以下值
R_OK -可读否
W_OK -可写否
X_OK -可执行否
F_OK -存在否
返回值:成功返回0,失败返回-1
//访问测试 #include#include int main(int argc,char* argv[]){ // ./a.out write.c // write.c 不存在 // write.c 存在,则判断可读,可写,不可执行 if(argc < 2){ fprintf(stderr,"用法:./a.out <文件名>n"); return -1; } printf("文件:%s ",argv[1]); if(access(argv[1],F_OK) == -1){//不存在 printf("不存在n"); }else{//存在 if(access(argv[1],R_OK) == -1){ printf("不可读,"); }else{ printf("可读,"); } if(access(argv[1],W_OK) == -1){ printf("不可写,"); }else{ printf("可写,"); } if(access(argv[1],X_OK) == -1){ printf("不可执行n"); }else{ printf("可执行n"); } } return 0; }
权限掩码
#include
mode_t umask(mode_t cmask);
>功能︰设置调用进程的权限掩码>参数:cmask 新权限掩码
>返回值:永远成功,返回原来的权限掩码。
umask函数所影响的仅仅是调用进程自身的权限掩码,对其父进程和兄弟进程没有任何影响,但是对子进程是有影响的。
umsk 0002是属于bash的,所以a.out默认0002,通过函数修改a.out,那么也只是a.out的权限掩码被设置成0333,
当然也可以命令行直接输入umask可以看权限掩码
//权限掩码
//权限掩码 #include#include #include #include int main(void){ // old ——》以前的掩码 0002 mode_t old = umask(0333); int fd = open("./umask.txt",O_RDWR | O_CREAT | O_TRUNC,0777); if(fd == -1){ perror("open"); return -1; } close(fd); //恢复以前的掩码 umask(old); return 0; }
修改文件大小
#include
int truncate(char const* path,off_t length);
int ftruncate(int fd,off_t length);
功能:修改指定文件大小
参数:path 文件路径
length 文件大小
fd 文件描述符
返回值:成功返回0,失败返回-1
该函数既可以把文件截短,也可以把文件加长,所有的改变均发生在文件的尾部,新增的部分用数字0填充
//修改文件大小 #include#include #include #include int main(void){ //打开文件 int fd = open("./trunc.txt",O_RDWR | O_CREAT | O_TRUNC,0664); if(fd == -1){ perror("open"); return -1; } //写入ABCDE --》 5字节 char* buf = "ABCDE"; if(write(fd,buf,strlen(buf)) == -1){ perror("write"); return -1; } //改小 if(ftruncate(fd,3) == -1){ perror("ftruncate"); return -1; } //改大 if(truncate("./trunc.txt",5) == -1){ perror("truncate"); return -1; } close(fd); return 0; }
改小:
改大



