1、当多个进程同时写一个文件时,有可能出现数据混乱,这个问题需要解决。解决方案:进程间的同步或文件锁。
2、文件锁就是当一个进程读写文件时,对其他进程进行读写的限制。
3、文件锁的结论:1)一个进程读,允许其他进程读,但不允许其他进程写。
2)一个进程写,其他进程既不能读也不能写。
4、文件锁是一个读写锁,包括读锁和写锁。
读锁是一个共享锁,允许其他进程读(共享),但不允许其他进程写(锁)。如果进程在读文件,就应该上读锁。
写锁是一个互斥锁,不允许其他进程读和写(互斥)。如果是写文件,就应该上写锁。
5、函数fcntl(fd, cmd, ...)当cmd为F_SETLK/F_SETLKW时,可以对文件进行上锁。
6、当使用文件锁时,第三个参数是一个结构体类型指针,结构体原型如下:
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 */
};
7、进程结束自动释放文件锁,但是最好还是程序员自己释放。
8、文件锁只是内存中的一个标识,不会真正锁定文件。fcntl()不能锁定write函数,只能锁定其他进程的加锁行为。
9、文件锁的正确用法是:在调用read()函数之前用fcntl()加读锁,能加再读,读完以后再释放锁;在调用write()函数之前用fcntl()加写锁,能加再写,写完之后在释放锁。但不管怎么加锁,类似vi的编辑器是无法锁定。
10、当F_SERLK加不上锁时,直接返回-1;而F_SETLKW加不上锁时,会继续等待,等到能加上锁为止。
11、F_GETLK不是获得当前锁,而是测试一下某个锁能不能加上,并不是真正的加锁。
实例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",O_RDWR | O_CREAT | O_TRUNC, 0666);
if(fd == -1){
perror("open"),exit(-1);
}
//定义锁
struct flock lock;
lock.l_type = F_WRLCK; //写锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 10;
lock.l_pid = -1;
//int res = fcntl(fd, F_SETLK, &lock);
int res = fcntl(fd, F_SETLKW, &lock);
if(res != -1){
int res = write(fd,"he",2);
if(res == -1){
perror("write1"),exit(-1);
}
sleep(5);
res = write(fd,"llo",3);
if(res == -1){
perror("write2"),exit(-1);
}
lock.l_type = F_UNLCK;
res = fcntl(fd, F_SETLK, &lock);
if(res == -1){
printf("释放锁失败!\n");
}
else{
printf("文件锁被释放!\n");
}
}
else{
printf("文件被锁定,无法读写!\n");
}
close(fd);
return 0;
}
1)传入型参数 给函数传值。比如int add(int, int);
2)传出型参数 带回函数结果,一般是指针类型。比如void add(int *sum){
int i = 0;
*sum = 0;
for(i = 1; i < 10; i++){
*sum += i;
}
}
3)传入传出型参数 先传入一个值,再带出一个值。比如void add(int, int, int *sum);其中sum就是传入传出型参数。
函数的返回值,可以用return直接返回,也可以使用传出型参数返回。
13、access()可以判断当前用户对文件的权限和文件是否存在。函数原型int access(char* fname, int mode);
参数:
fname代表文件名
mode代表文件模式,取值可为:
F_OK 文件是否存在
R_OK 是否具有读权限
W_OK 是否具有写权限
X_OK 是否具有可执行权限
成功返回0,失败返回-1。
实例:
/*
文件权限函数access演示
*/
#include <stdio.h>
#include <unistd.h>
int main(){
if(access("a.txt",R_OK) == 0){
printf("可读!\n");
}
if(access("a.txt",W_OK) == 0){
printf("可写!\n");
}
if(!access("a.txt",X_OK)){
printf("可执行!\n");
}
if(!access("a.txt",F_OK)){ //在读文件之前,检测文件是否存在
printf("文件存在!\n");
}
return 0;
}
chmod 修改文件权限,比如chmod("a.txt",0666);
truncate/ftruncate 指定文件的大小,比如truncate("a.txt",100);
remove 删除文件或空目录
rename 修改文件名
umask 修改新建文件时,系统默认的权限屏蔽字。系统默认屏蔽其他用户的写权限,-0002。
函数原型mode_t umask(mode_t)传入新的权限,返回之前的权限屏蔽字,用于处理之后的恢复。
mmap 可以映射物理内存,但也可以映射文件,默认情况下映射文件,映射物理内存需要加MAP_ANONYMOUS标识。
实例:
/*
文件函数演示
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(){
chmod("a.txt",0666);
truncate("a.txt",100);
int fd1 = open("aa",O_RDWR | O_CREAT,0666);
if(fd1 == -1){
perror("open1"),exit(-1);
}
mode_t old = umask(0022);
int fd2 = open("bb",O_RDWR | O_CREAT,0666);
if(fd2 == -1){
perror("open2"),exit(-1);
}
umask(old);
int fd3 = open("cc",O_RDWR | O_CREAT,0666);
if(fd3 == -1){
perror("open3"),exit(-1);
}
close(fd1);
close(fd2);
close(fd3);
return 0;
}
目录相关函数:
mkdir() 创建一个目录
rmdir() 删除一个空目录
chdir() 切换当前目录
getcwd() 取当前目录(返回绝对路径形式)
读取目录函数
opendir() 打开一个目录,返回目录流
readdir() 读目录的一个子项(子目录/子文件)
效果相当于命令:ls 目录
closedir() 关闭目录流(不写也可以)
实例:
/*
目录函数演示
*/
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
void printall(const char* path){ //递归函数
DIR* dir = opendir(path);
if(dir){ //目录是一个子项
struct dirent* dirent = NULL;
while(dirent = readdir(dir)){
if(strcmp(".",dirent -> d_name) == 0 || strcmp("..",dirent -> d_name) == 0){ //跳过目录“.”和“..”,避免死循环
continue;
}
if(dirent -> d_type == 4){ //目录
printf("[%s]\n",dirent -> d_name);
char buf[100] = {};
sprintf(buf,"%s/%s",path, dirent -> d_name);
printall(buf);
}
else{ //文件
printf("%s\n",dirent -> d_name);
}
}
}
else{
return;
}
}
int main(){
printall("../");
return 0;
}
知识补充:
使用递归的条件:
1、使用递归后,问题简化而不是复杂化。
2、递归必须有退出条件。
3、使用递归要注意效率问题。