UC成长之路8

回顾:
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;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值