Linux 是什么 -> 操作系统的内核
基于Linux 内核的发布版本:Ubantu、centOs、FreeBSD、Android
计算机系统的架构:硬件 -> 硬件接口 -> CPU -> 内核 -> 应用层
Linux 内核的五个子系统: 进程调度、内存管理、文件管理、设备管理、网络
Linux下一切皆文件
文件I / O一般用到5个函数:open , write , read , lseek , close
文件 I/O 不带缓存区,标准 I/O 带有缓存区
1. open 函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode)
第一个参数:文件名或文件路径
第二个参数:
- O_RDONLY 只读打开。
- O_WRONLY 只写打开。
- O_RDWR 读、写打开。
三种方式只能选取一种,另外可与其他方式进行或运算,如O_CREAT (创建文件)
第三个参数:代表创建文件的权限,如0644,注意,只有在创建文件时,才需要填写,一般情况下可以省略
返回值:若成功为文件描述符,若出错为-1
文件描述符:对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。内核在进程打开的时候,会用一个不大于1024的整数来记录文件的位置,其实这个就相当于是一个索引
#include <unistd.h>
#define STDIN_FILENO 0 /* 标准输入 */
#define STDOUT_FILENO 1 /* 标准输出 */
#define STDERR_FILENO 2 /* 标准出错*/
任何一个进程,在启动时,系统便默认的为它打开这三个文件描述符。
2. write 函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
第一个参数:文件描述符
第二个参数:要写入的数据缓存区
第三个参数:写入的字节数
返回值:若成功为已写的字节数,若出错为- 1
如果 buf 的长度等于或大于 count ,直接写入 count 字节的数据;如果 buf 的长度小于 count ,则会阻塞,等待内核缓存区继续有内容填充后,将不够的补全,再写入。
用户态:由创建的进程直接进行管理的状态,叫做用户态
内核态:由内核进行管理的状态,叫做内核态,或者系统态
stdio 会产生在用户态,根据进程的管理,和进程相关的函数调用栈
write 会存在于内核态
进程和系统相关的操作,其实都是会被记录的
进程 -> 内核 -> 申请内存 -> 传递数据
数据由内核写入,通过系统调用 write 函数完成
3. read 函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
第一个参数:文件描述符
第二个参数:要读取的数据缓存区
第三个参数:读取的字节数
返回值:实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据
如果bufsize <= count , 读入bufsize;如果bufsize >= count , 只读入count,后面的值丢弃
(额外补充)stat 函数:用于获取文件信息
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
第一个参数:文件名或文件描述符
第二个参数:数据类型为stat 的结构体
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* file type and mode */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
}
4. fcntl 函数:用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
第一个参数:文件描述符 fd
fcntl函数功能依据cmd的值的不同而不同。参数对应功能如下:
- 复制一个现有的描述符(cmd=F_DUPFD)
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
- 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)
F_DUPFD
与dup函数功能一样,复制由fd指向的文件描述符,调用成功后返回新的文件描述符,与旧的文件描述符共同指向同一个文件。
F_GETFL 和 F_SETFL
获取或设置文件状态标志,可获取O_APPEND,O_NONBLOCK,O_SYNC和O_ASYNC。三个存取方式标志( O_RDONLY,O_WRONLY,O_RDWR )并不各占1位。(这三个标志的值各是0、1和2,由于历史原因。这三种值互斥,一个文件只能有这三种值之一。)因此首先必须用屏蔽字O_ACCMODE取得存取方式位,然后将结果与这三种值相比较。
eg:
- 获取文件的flags,即open函数的第二个参数:
flags = fcntl(fd,F_GETFL,0);
- 设置文件的flags:
fcntl(fd,F_SETFL,flags);
- 增加文件的某个flags,比如文件是阻塞的,想设置成非阻塞:
flags = fcntl(fd,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
- 取消文件的某个flags,比如文件是非阻塞的,想设置成为阻塞:
flags = fcntl(fd,F_GETFL,0);
flags &= ~O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
F_GETLK,F_SETLK或F_SETLKW
- F_GETLK 获取记录锁
- F_SETLK 尝试上锁,不阻塞,如果其它进程已经锁住了,无法上锁时,直接返回失败;如果没有被锁住的话,直接返回上锁成功
- F_SETLKW 会阻塞,如果其它进程已经锁住了,无法上锁时,fcntl函数会等待其它进程释放锁为止才返回
当fcntl的cmd是F_GETLK、F_SETLK或F_SETLKW。第三个参数是一个指向flock结构的指针。
struct flock {
short l_type; /* 记录锁类型,F_RDLCK,F_WRLCK或F_UNLCK */
off_t l_start; /* 起始位置偏移量,单位字节,是相对于l_whence而言 */
short l_whence; /* SEEK_SET,SEEK_CUR,或SEEK_END */
off_t l_len; /* 长度,单位字节,值为0时表示一直到文件尾 */
pid_t l_pid; /* 使用F_GETLK时返回此值 */
}
锁类型:F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁一个区域)
为了锁整个文件,通常的方法是将l_start说明为0,l_whence说明为SEEKSET,l_len说明为0。
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
给文件加读锁时,该描述符必须是读打开;加写锁时,该描述符必须是写打开。
下面是文件在各种状态时能加锁的规则:
文件区加锁状态 | 是否能加读锁 | 是否能加写锁 |
---|---|---|
无锁 | 是 | 是 |
读锁 | 是 | 否 |
写锁 | 否 | 否 |
死锁:
如果两个进程相互等待对方持有并且不释放(锁定)的资源时,则这两个进程就处于死锁状态。如果一个进程已经控制了文件中的一个加锁区域,然后它又试图对另一个进程控制的区域加锁,则它就会睡眠,在这种情况下,有发生死锁的可能性。
关于锁的使用几个注意点:
- 锁住一个资源的时候需要尽量短,对资源上锁后需要尽可能快的释放锁(时间长的时候容易产生效率低下)
- 在锁住一个资源的时候,在未释放前尽量不要去再申请另一个锁,避免死锁
- 在上锁与释放锁之间的代码尽可能的少
5. dup2 函数
#include <unistd.h>
int dup2(int oldfd, int newfd);
- 用参数newfd指定新文件描述符的数值。若参数newfd已经被程序使用,则系统就会将newfd所指的文件关闭,若newfd等于oldfd,则返回newfd,而不关闭newfd所指的文件。dup2所复制的文件描述符与原来的文件描述符共享各种文件状态。共享所有的锁定,读写位置和各项权限或flags等.
- 若dup2调用成功则返回新的文件描述符,出错则返回-1。 成功返回时,目标描述符(dup2函数的第二个参数)将变成源描述符(dup2函数的第一个参数)的复制品,换句话说,两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件。