回顾:
UC成长之路1
UC成长之路2
UC成长之路3
UC成长之路4
UC成长之路5
UC成长之路6
UC成长之路7
一、文件锁的使用
- 按功能分为读锁和写锁
- 读锁也叫共享锁,写锁也叫互斥锁
- 多个进程访问同一个文件的时候,同为读操作时进程之间不受影响,但不能有的进程在读,有的却在写。
- 锁的实现分为两类:建议锁和强制锁
- 多进程使用锁对文件访问的步骤:
- 加锁
- 失败,不操作。成功,对文件进行操作
- 操作完毕,解锁
- 系统为我们提供了一个系统调用fcntl(2)。这个系统调用用于控制文件描述符。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
//功能:操作文件描述符
//参数
//fd:指定了操作的文件描述符
//cmd:对文件描述符的操作指令
//F_SETLK, F_SETLKW, and F_GETLK用于文件锁的指令
//...:可变参数,参数的个数和类型取决于cmd
//返回值:成功返回0,错误返回-1并设置errno
struct flock {
...
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock
(set by F_GETLK and F_OFD_GETLK) */
...
};
eg1:pA.c 加文件读锁,开两个进程在同一文件上加读锁,验证同时加读锁不会发生冲突
pA.c
#include <stdio.h>
#include "t_file.h"//在UC成长之路6中有说到
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
struct flock lock;
//以只讀方式打開文件
int fd=open(argv[1],O_RDONLY);
if(fd==-1){
perror("open");
return -1;
}
//初始化鎖的內容
lock.l_type=F_RDLCK;//指定鎖的類型
//指定鎖的內容
lock.l_whence=SEEK_SET;
lock.l_len=6;
lock.l_start=0;
//對文件加讀鎖
int f=fcntl(fd, F_SETLK,&lock);
if(f==-1){
perror("fcntl");
return -1;
}
//加鎖成功
printf("fcntl success...");
getchar();
//關閉文件描述符,有可能造成鎖的丟失
close(fd);
return 0;
}
eg2:eg1:pA.c 加文件读锁,pB.c加写锁,先运行读锁进程在运行写锁进程,写锁进程在非阻塞时直接结束,阻塞是等待读锁进程释放
pB.c
#include <stdio.h>
#include "t_file.h"
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
struct flock lock;
//以只寫方式打開文件
int fd=open(argv[1],O_WRONLY);
if(fd==-1){
perror("open");
return -1;
}
//初始化鎖的內容
lock.l_type=F_WRLCK;//指定鎖的類型
//指定鎖的內容
lock.l_whence=SEEK_SET;
lock.l_len=6;
lock.l_start=0;
//對文件加寫鎖
int f=fcntl(fd, F_SETLK,&lock);//F_SETLK非阻塞,F_SETLKW阻塞
if(f==-1){
perror("fcntl");
return -1;
}
//加鎖成功
printf("fcntl success...");
getchar();
//關閉文件描述符,有可能造成鎖的丟失
close(fd);
return 0;
}
eg3:程序pC.c测试是否可以加读锁
pC.c
#include <stdio.h>
#include "t_file.h"
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
struct flock lock;
//以只讀方式打開文件
int fd=open(argv[1],O_RDONLY);
if(fd==-1){
perror("open");
return -1;
}
//初始化鎖的內容
lock.l_type=F_RDLCK;//指定鎖的類型
//指定鎖的內容
lock.l_whence=SEEK_SET;
lock.l_len=6;
lock.l_start=0;
//對文件加讀鎖
int f=fcntl(fd, F_GETLK,&lock);
if(f==-1){
perror("fcntl");
return -1;
}
if(lock.l_type==F_UNLCK){
//加鎖成功
printf("may fcntl readlock...\n");
}
else{
//不能加鎖
printf("have a conflicting lock...\n");
printf("pid of hold lock:%d\n", lock.l_pid);
}
getchar();
//關閉文件描述符,有可能造成鎖的丟失
close(fd);
return 0;
}
二、标准库函数和系统调用之间的关系(文件操作为例)
- 标准库函数:fopen(3), fclose(3), fputc(3), fgetc(3)
- 系统调用:open(2), close(2), read(2), write(2)
eg:以只读方式打开文件,使用库函数fopen()和fclose()
file.c
#include <stdio.h>
int main(int argc, char* argv[])
{
FILE* fp=fopen(argv[1], "r");
if(!fp){
perror("fopen");
return -1;
}
printf("open %s success...\n", argv[1]);
fclose(fp);
return 0;
}
使用命令gcc -E file.c -o file.i
,预编译后停下来,生成后缀为 .i
的预编译文件。观察file.i
文件可以发现fopen()和fclose()的底层实现原来是系统调用open()和close()
- fopen(3)调用的时候,首先分配一块内存空间,然后调用open(2)得到一个文件描述符,将文件描述符存储到FILE结构的_fileno成员中。
- fgetc(3)首先从缓存中获取数据,如果缓存中有数据,直接返回。如果缓冲中没有,调用read(2)将数据从文件读取到缓冲区中,然后再从缓冲区中获取到数据返回。
- fputc(3)如果缓冲区有足够的空间接纳字符,将字符直接放入缓冲区即可返回。如果缓冲区没有足够的空间接纳字符,调用write(2)将缓冲区的数据写入文件,清理缓冲区,再将字符放入缓冲区返回。
- fflush(3)可以及时清理缓冲区,及时将缓冲区的内容写到文件中。
- fclose(3)首先清理缓冲区数据到文件中,然后释放缓冲区,最后调用close(2)关闭文件。
使用库函数的操作的文件称为缓冲文件,使用系统调用操作的文件称为非缓冲文件。
三、文件操作的杂项
查文件自学:
- access(2)
- getcwd(3)
- mkdir(2)
- rmdir(2)
- umask(2)
- unlink(2)
- link(2)
- rename(2)
- symlink(2)
- remove(3)
- chmod(2)
四、进程管理的基础
1、进程的基础
- 进程是资源分配的基本单位。进程也是程序在执行过程中,对使用的资源描述。每个进程都有自己的pid,还有自己的PCB,PCB中记录了进程使用资源的情况。
- 所有的应用进程组成了一棵进程树,树根是init进程也就是1号进程,pid为1。
- 如何查看进程树?使用
pstree
命令查看 - 进程间的关系:
父子关系
和兄弟关系
两种关系 - 查看进程的信息:
ps -aus
- 查看进程的更新:
top
2、创建进程
- 系统提供了fork(2),用于创建子进程
#include <unistd.h>
pid_t fork(void);
//功能:
//参数:void
//返回值:
//失败返回-1,在父进程里返回-1,子进程没有被创建errno被设置;
//成功父进程里返回子进程的pid,子进程返回0
eg:使用fork创建一个子进程
#include <stdio.h>
#include <unistd.h>
int main(void)
{
//創建子進程
pid_t child=fork();
if(child==-1)
{
perror("fork");
return -1;
}
if(child==0){
printf("child process...\n");
}
else{
printf("parent process...\n");
}
printf("hehe...\n");
return 0;
}
- 异步和同步
- 孤儿进程:父进程已经终止,把已经终止的父进程的子进程过继给init进程,子进程的父进程就是init进程。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
//創建子進程
pid_t child=fork();
if(child==-1)
{
perror("fork");
return -1;
}
if(child==0){
printf("child process...%d\n",getpid());
printf("parent of child....%d\n", getppid());
}
else{
printf("parent process...%d\n", getpid());
}
printf("hehe...\n");
return 0;
}
3、进程的退出
-
return和exit(3)的区别:return只是从函数中返回,在main函数中返回,代表进程的结束。exit(3)是终止当前进程。
-
exit(3)
#include <stdlib.h>
void exit(int status);
//功能:正常的终止进程
//参数
//status:status&0377被传送给父进程
//返回值:
eg:exit(3)的使用
#include <stdlib.h>
int main(void)
{
//exit(1);//1
exit(-1);//2
}
-
补充:
- $?:代表上一个命令执行后的退出状态
- echo $? :如果返回值是0,就是执行成功;如果是返回值是0以外的值,就是失败。
-
遗言函数:进程在终止要处理一些事情,调用函数执行这些事情,这样的函数称为遗言函数。
-
在进程终止之前要注册遗言函数,在进程终止的时候执行遗言函数。
-
注册遗言函数:atexit(3), on_exit(3)
#include <stdlib.h>
int atexit(void (*function)(void));
//功能:注册遗言函数,在进程终止的时候执行
//参数
//function:指针类型的变量,存放遗言函数的入口地址
//返回值:成功返回0,错误返回非0
//注意:1、调用的顺序和注册的顺序相反;
//2、同一个函数被注册一次就被调用一次
//3、子进程继承父进程的遗言函数
eg:遗言函数的使用
int main(void)
{
//注册遗言函数
atexit(doit);
getchar();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
void doit(void)
{
printf("I will die...\n");
}
void dou(void)
{
printf("U will die...\n");
}
int main(void)
{
//注册遗言函数
atexit(doit);
atexit(dou);
getchar();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void doit(void)
{
printf("I will die...%d\n", getpid());
}
void dou(void)
{
printf("U will die...%d\n", getpid());
}
int main(void)
{
//注册遗言函数
atexit(doit);
atexit(dou);
pid_t f = fork();
if(f==-1){
perror("fork");
return -1;
}
if(f==0){
exit(0);
}
else{
}
getchar();
return 0;
}