Unix高级编程:库函数与系统调用函数区别、文件锁、进程基础

一、库函数和系统调用之间的关系和区别(文件操作函数说明)
fopen(3)
FILE *fopen(const char *path, const char *mode);
当使用fopen(3)打开文件的时候,发生了什么?
1)首先分配了一块内存空间,用于文件内容的缓冲
2)然后调用open(2)


fputc(3)
int fputc(int c, FILE *stream);
首先将数据放入fopen(3)开辟的那块内存空间里:
1)如果内存空间不满,不会立即写入到文件,当清理缓存的时候或者或者缓存满的时候,调用"write"(2)将缓存里的内容写到文件。
2)如果内存空间满的时候,调用write(2)将缓存里的数据写入到文件,然后将fputc的内容写到缓存里。


fgetc(3)
int fgetc(FILE *stream);
调用fgetc(fp)的时候,首先到fp指向的内存空间里去找一个字符:
1)如果空间里有字符,立即返回。
2)如果没有字符,调用"read(fp->_fileno, buf, len)",buf指向了fp的内存空间。这个时候将文件的内容读取到fopen(3)开辟的那块内存空间里,这个时候再从这块内存空间里将字符读取。


fclose(3)
int fclose(FILE *fp);
首先将缓存里的数据写入到文件,然后释放缓存、调用close(2)关闭文件


"缓冲文件"
"非缓冲文件"


fflush(3)
int fflush(FILE *stream);
刷新缓冲(清理缓存),原因:感觉写入文件其实在数据在缓冲,并没有实际写入,调用fflush(3)即可。
stdin 0 标准输入
stdout 1 标准输出
stderr 2 标准错误输出


printf("hhhh"); //printf占用行缓存,没满有时不输出,加\n输出,缓冲所致
1) \n //加\n,也能输出,方法1
2) fflush(1); //加标准输出,也能输出,方法2


二、文件锁(建议锁/强制锁暂不讲)
多个进程同时访问一个文件的时候,这个文件就成为"临界资源",这个时候对这个文件的访问需要同步。


同步和异步
"同步" 事情A和事情B,只有在事情A完成之后,再进行事情B,那么AB同步。
"异步" 事情A和事情B,谁先执行都可以,执行无先后顺序,那么AB异步。


为了避免在读写同一个文件的同一个区域时发生冲突,进程之间应该遵循以下规则:
1)如果一个进程"正在写",那么"其它进程既不能写也不能读"
2)如果一个进程"正在读",那么"其它进程不能写但是可以读"


为了实现同步,在这里使用对文件加锁。"建议锁"/"强制锁",两种所都有:
"读锁" (共享锁)对一整个文件或特定区域可以加多把读锁
"写锁" (互斥锁)对一整个文件或特定区域只能加一把写锁


"fcntl"(2) //使用该系统调用函数完成对文件的加锁
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:操作文件描述符
参数:
"fd" open(2)的返回值,指定要操作的文件(描述符)
"cmd" 命令。对文件描述符进行操作的命令
F_SETLK 设置锁-非阻塞模式,进程遇锁立即以错误返回,返回错误
F_SETLKW 设置锁-阻塞模式,进程遇锁将等待冲突锁直到锁被释放
F_GETLK 获取锁
"..." 可变参数(需要or不需要"取决与cmd参数")
"..."=="lock"
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
                       (F_GETLK only) */
};
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
/*举例验证文件锁的使用,代码参见 processA.c & processB.c*/
/* processA.c */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void) {
    int fd; 
    fd = open("aaa.txt", O_RDWR);//打开一个文件
    if(fd == -1) {
        perror("open");//打开失败
        return 1;
    }   
    struct flock lock; //初始化文件锁
    lock.l_type = F_RDLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    int ret = fcntl(fd, F_SETLK, &lock);//设置锁
    if(ret == -1) {
        perror("fcntl");
        return 2;
    }   
    printf("read lock success...\n");
    sleep(20);
    close(fd);//关闭文件
    printf("read lock release...\n");
    return 0;
}
/* processB.c */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void) {
    int fd; 
    fd = open("aaa.txt", O_RDWR);//打开文件
    if(fd == -1) {
        perror("open");//打开失败
        return 1;
    }   
    struct flock lock;//初始化文件锁
    lock.l_type = F_WRLCK;//读锁可直接进行/写锁需等待A完成
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    int ret = fcntl(fd, F_SETLKW, &lock);//设置锁
    if(ret == -1) {
        perror("fcntl");
        return 2;
    }   
    printf("read lock success...\n");
    close(fd);//关闭文件
    return 0;
}
此两个程序运行A的同时,在另一个标签页运行B,B可设置读锁,但B的写锁要等待A运行完毕才会执行写锁成功。




三、文件操作杂项


"chdir"(2) //cd 命令就是调用封装的 chdir 系统函数
#include <unistd.h>
int chdir(const char *path);
功能:改变工作路径
参数:"path" 目标工作路径
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


"mkdir"(2) //mkdir 命令就是调用封装的 mkdir 系统函数
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
功能:创建一个文件夹或目录
参数:
"pathname" 要创建的文件夹名字
"mode" 新的文件夹的权限,例如 0664 或 mode & ~umask & 0777
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


"getcwd"(3) //pwd 命令就是调用封装的 getcwd 系统函数
#include <unistd.h>
char *getcwd(char *buf, size_t size);
功能:获取当前的工作路径
参数:
"buf" 将当前路径的绝对路径名字拷贝到buf指定的地址空间里
"size" 拷贝的长度
返回值:
成功 - 当前工作路径的首地址指针
失败 - 返回 NULL,errno被设置


"rmdir"(2) //rm -r 命令就是调用封装的 rmdir 系统函数
#include <unistd.h>
int rmdir(const char *pathname);
功能:删除一个目录
参数:"pathname" 要删除的文件夹名字
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


"link"(2) / "symlink"(2) //其中link可做命令行指令使用
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
功能:为文件创建一个新名字 - 硬链接(非拷贝内容)
参数:
"oldpath" 原文件路径
"newpath" 新文件路径
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


"unlink"(2)
#include <unistd.h>
int unlink(const char *pathname);
功能:删除一个硬链接文件名字
参数:"pathname" 文件名
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


"rename"(2) //mv 命令就是调用封装的 rename 系统函数
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
功能:更改文件名或文件路径
参数:
"oldpath" 原文件名/路径
"newpath" 新文件名/路径
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


"chmod"(2)
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
功能:改变文件的权限
参数:
"path" 文件名
"mode" 权限属性宏
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置




文件内容小结:
1)文件的管理(open/read/write/close/lseek/dup/dup2/fcntl/mmap...)
2)文件系统管理(stat/opendir/closedir/readdir/telldir...)
3)文件的权限、类型、软链接、硬链接、属主、属组都需弄清楚




四、进程的基本概念
进程和程序
"程序是静态的",是被存储在磁盘上,包含机器指令和数据的文件。
"进程是动态的",是被装载到内存中,被处理器操作的代码和数据。
"一个程序可被运行为多个进程"


进程快照
命令行:ps
  PID TTY          TIME CMD
 2346 pts/0    00:00:00 bash
12376 pts/0    00:00:00 ps
PID:进程标识
TTY:控制终端次设备号
TIME:进程运行时间
CMD:进程启动命令
命令行:ps aux
VSZ:占用虚拟内存大小(KB)
RSS:占用物理内存大小(KB)


每个进程都有自己的"pid",都有自己的户口本"PCB"(进程控制器)。
PID:即Process Identification,"进程标识"。每个进程都有这么一个"非负整数形式的唯一编号"。任何时刻都是唯一的,但是"可以重用,进程被终止并被回收"以后,其PID就可以被其他进程所用。
PCB 是由操作系统的内核kernel来完成的,内核通过来管理PCB来对进程进行管理。
0 号进程:调度进程(交换进程swapper),系统内核的一部分
1 号进程:init进程,以超级用户运行的普通进程,永不终止
2 号进程:页守护进程,负责虚拟内存系统的分页操作


在linux系统中,用户进程是以一棵树来组织的。


"pstree"命令查看这棵树,格式:pstree


进程和进程之间的父子关系或者兄弟关系,亲缘关系。
"init进程"是"1号进程"


怎么去创建一个子进程?使用系统调用函数fork(2)来创建


"fork"(2)
#include <unistd.h>
pid_t fork(void);
功能:创建一个子进程
参数:void
返回值:
成功 - 在父进程里子进程的"pid被返回",在子进程里返回 0 
失败 - 在父进程里返回 -1,子进程没有被创建,errno被设置
/*举例验证fork创建子进程,代码参见 fork.c*/
#include <stdio.h>
#include <unistd.h>
int main(void) {
    pid_t pid;
    pid = fork(); //此行开始下面就是2份代码在执行,父子进程先调用谁,由操作系统决定,人为无法判断
    if(pid < 0) {
        perror("fork");
        return 1;
    }   
    if(pid == 0) {
        printf("I am child process...\n");
    }   
    printf("I am father process...\n");
    return 0;
}


子进程创建完成,子进程和父进程的执行是"异步"的。
子进程是父进程的不完全副本,子进程的数据区、BSS区、堆栈区(包括I/O流缓冲区),甚至参数和环境区都从父进程拷贝,"唯有代码区与父进程共享"。
调用fork函数前的代码只有父进程执行,fork函数成功返回后的代码父进程和子进程都会执行,受逻辑控制进入不同分支。
fork函数调用一次,在父进程和子进程中各返回一次,在父进程中返回所创建子进程的PID,而在子进程中返回0。
fork函数成功返回以后,父子进程各自独立运行,其被调度的先后顺序并不确定。


"getpid"(2)/"getppid"(2)
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); //获取自己的pid
pid_t getppid(void); //获取自己父进程的pid
/*代码演示*/
    printf("pid = %d\n", getpid());
    printf("ppid = %d\n", getppid());


实例:
父-子(父)-子(父)-子
/*代码*/
#include <stdio.h>
#include <unistd.h>
void myfork(int c) {
    pid_t pid;
    if(c-- == 1) {
        return ;
    }   
    pid = fork();
    if(pid == 0) {
        printf("pid = %d\n", getpid());
        printf("ppid = %d\n", getppid());
        printf("----------------------\n");
        myfork(c);
    }   
    else
        sleep(5);
    return;
}
int main(void) {
    int count = 4;
    myfork(count);
    return 0;
}


进程的退出:
"exit"(3) 和 return 的区别
return 只是完成函数的返回
exit 函数是进程的退出
在 main 函数中 return 返回之后,进程还没有结束
而调用exit函数的时候,进程就完全结束了
事实上,main函数里的return语句也会被编译器处理为类似对exit函数的调用,所以可以认为 return x 等价于 exit(x)。


"_exit"(2)
#include <unistd.h>
void _exit(int status);
功能:退出进程
参数:"status" 文件描述符
返回值:无返回值


在进程终止之前可以做一些处理工作
"atexit"(3)
#include <stdlib.h>
int atexit(void (*function)(void));
功能:向进程注册,进程终止之前调用的函数
参数:void (*function)(void) 进程结束时要调用的函数,无参数
返回值:
成功 - 返回 0
失败 - 返回非 0
/*举例验证,代码参见 atexit.c*/
#include <stdio.h>
#include <stdlib.h>
void handle(void) {
    printf("oh , my god..!\n");
    return;
}
int main(void) {
    atexit(handle);
    sleep(5);
    return 0;//在return之后,在进程退出之前,printf
}


"on_exit"(3)
#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);
功能:向进程注册,进程终止之前调用的函数
参数:
void (*function)(int , void *) 进程结束时要调用的函数,两个参数
function函数里的 int 是 return 的数字传到int里 
"arg" function函数里的第 2 个参数
返回值:
成功 - 返回 0
失败 - 返回非 0
/*举例验证,代码参见 on_exit.c*/
#include <stdio.h>
#include <stdlib.h>
void handle(int x, void *arg) {
    printf("%d\t%s\n", x, (char *)arg);
    return ;
}
int main(void) {
    on_exit(handle, "tarena");
    sleep(5);
    return 2;// 或 exit(5)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜源Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值