德州网站怎样建设,重庆seo排名收费,网站降权是什么意思,手机网站快速排名 软件文章目录1. 系统调用与库函数1.1 什么是系统调用1.2 系统调用的实现1.3 系统调用和库函数的区别2. 虚拟内存空间3. 错误处理函数4. C 库中 IO 函数工作流程5. 文件描述符6. 常用文件 IO 函数6.1 open 函数6.2 close 函数6.3 write 函数6.4 read 函数6.5 lseek 函数7. 文件操作相…
文章目录1. 系统调用与库函数1.1 什么是系统调用1.2 系统调用的实现1.3 系统调用和库函数的区别2. 虚拟内存空间3. 错误处理函数4. C 库中 IO 函数工作流程5. 文件描述符6. 常用文件 IO 函数6.1 open 函数6.2 close 函数6.3 write 函数6.4 read 函数6.5 lseek 函数7. 文件操作相关函数7.1 stat 函数7.2 access 函数7.3 chmod 函数7.4 chown 函数7.5 truncate 函数7.6 link 函数7.7 symlink 函数7.8 readlink 函数7.9 unlink 函数8. 文件描述符复制8.1 概述8.1 dup 函数8.2 dup2 函数8.3 示例分析9. fcnlt 函数10. 目录相关操作10.1 getcwd 函数10.2 chdir 函数10.3 opendir 函数10.4 readdir 函数10.5 closedir 函数10.6 mkdir 函数10.7 rename 函数11. 时间相关函数11. 0 时间相关概念11.1 时间获取函数11.1.1 time 函数11.1.2 clock 函数11.1.3 gettimeofday() 函数11.2 日历时间转换为分解时间11.2.1 localtime() 函数11.2.2 gmtime() 函数11.3 分解时间转换为日历时间11.3.1 mktime() 函数11.4 将时间转换为字符串相关函数11.4.1 asctime() 函数11.4.2 ctime() 函数11.4.3 strftime() 与 strptime() 函数11.5. 时间差计算函数11.5.1 difftime() 函数11.6 线程安全的时间转换函数1. 系统调用与库函数
1.1 什么是系统调用
系统调用顾名思义说的是操作系统提供给用户程序调用的一组“特殊”接口。用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件可以通过时钟相关的系统调用获得系统时间或设置定时器等。
从逻辑上来说系统调用可被看成是一个内核与用户空间程序交互的接口——它好比一个中间人把用户进程的请求传达给内核待内核把请求处理完毕后再将处理结果送回给用户空间。
系统服务之所以需要通过系统调用来提供给用户空间的根本原因是为了对系统进行“保护”因为我们知道 Linux 的运行空间分为内核空间与用户空间它们各自运行在不同的级别中逻辑上相互隔离。
所以用户进程在通常情况下不允许访问内核数据也无法使用内核函数它们只能在用户空间操作用户数据调用用户空间函数。比如我们熟悉的“hello world”程序执行时就是标准的用户空间进程它使用的打印函数 printf() 就属于用户空间函数打印的字符“hello word”字符串也属于用户空间数据。
但是很多情况下用户进程需要获得系统服务调用系统程序这时就必须利用系统提供给用户的“特殊接口”——系统调用了它的特殊性主要在于规定了用户进程进入内核的具体位置。
换句话说用户访问内核的路径是事先规定好的只能从规定位置进入内核而不准许肆意跳入内核。有了这样的陷入内核的统一访问路径限制才能保证内核安全无误。我们可以形象地描述这种机制作为一个游客你可以买票要求进入野生动物园但你必须老老实实地坐在观光车上按照规定的路线观光游览。当然不准下车因为那样太危险不是让你丢掉小命就是让你吓坏了野生动物。
1.2 系统调用的实现
系统调用是属于操作系统内核的一部分的必须以某种方式提供给进程让它们去调用。CPU 可以在不同的特权级别下运行而相应的操作系统也有不同的运行级别用户态和内核态。运行在内核态的进程可以毫无限制的访问各种资源而在用户态下的用户进程的各种操作都有着限制比如不能随意的访问内存、不能开闭中断以及切换运行的特权级别。显然属于内核的系统调用一定是运行在内核态下那如何从用户态切换到内核态呢
答案是软件中断。软件中断和我们常说的中断硬件中断不同之处在于它是通过软件指令触发而并非外设引发的中断也就是说又是编程人员开发出的一种异常该异常为正常的异常。操作系统一般是通过软件中断从用户态切换到内核态。
1.3 系统调用和库函数的区别
Linux 下对文件操作有两种方式系统调用system call和库函数调用Library functions。
库函数由两类函数组成 1不需要调用系统调用不需要切换到内核空间即可完成函数全部功能并且将结果反馈给应用程序如 strcpy()、bzero() 等字符串操作函数。 2需要调用系统调用需要切换到内核空间这类函数通过封装系统调用去实现相应功能如 printf()、fread() 等。
系统调用是需要时间的程序中频繁的使用系统调用会降低程序的运行效率。当运行内核代码时CPU 工作在内核态在系统调用发生前需要保存用户态的栈和内存环境然后转入内核态工作。系统调用结束后又要切换回用户态。这种环境的切换会消耗掉许多时间 。
2. 虚拟内存空间 区分程序和进程程序就是磁盘上的代码包括可执行程序和代码它只占用磁盘空间不占用内存空间进程运行中的程序会将代码加载到内存中进行运行并分配一些列资源。进程的知识在后续的文章中还会讲解。 虚拟地址空间是不存在的而是人为想象出来的。可执行程序运行起来后就会产生一个虚拟地址空间如果运行结束虚拟地址空间就结束了。这个虚拟地址空间是虚拟出来的大小由计算机决定比如32位的操作系统中虚拟地址空间为 4G 虚拟地址空间最终会被逻辑管理单元MMU映射到真实的物理内存中。
每个进程都会分配虚拟地址空间在32位机器上该地址空间为4G 。在进程里平时所说的指针变量保存的就是虚拟地址。当应用进程使用虚拟地址访问内存时处理器CPU会将其转化成物理地址MMU。
MMU将虚拟的地址转化为物理地址。这样做的好处在于
进程隔离更好的保护系统安全运行屏蔽物理差异带来的麻烦方便操作系统和编译器安排进程地址
【补充】如上图中 a.out 可执行文件是存放在磁盘中执行可执行文件后文件会被加载到内存中执行形成进程。所以此时即使删除掉磁盘中的 a.out 文件也不会影响正在执行的该进程。
3. 错误处理函数
查看错误代码是调试程序的一个重要方法。c 语言中errno 是记录系统的最近一次错误代码。该代码是一个 int 型的值在 errno.h 中定义。
当 Linux C API 函数发生异常时一般会将 errno 全局变量赋一个整数值不同的值表示不同的含义可以通过查看该值推测出错的原因。Linux 下也提供了 相关的库函数方便的将 errno 整数值转换成描述了错误的字符串
strerror()将错误号转换成字符串。 函数原型char *strerror(int errnum)头文件string.h参数 errnum – 错误号通常是 errno。返回值该函数返回一个指向错误字符串的指针该错误字符串描述了错误 errnum。 perror()输出一个描述性错误消息。 函数原型void perror(const char *str)头文件stdio.h参数 str – 这是 C 字符串包含了一个自定义消息将显示在原本的错误消息之前。返回值该函数不返回任何值。
测试程序
#include errno.h //errno
#include stdio.h //fopen
#include string.h //strerror(errno)int main() {FILE* fp fopen(xxxx, r); // 打开一个不存在的文件。if (NULL fp) {//打印错误码printf(errno%d\n, errno);// strerror把errno的数字转换成相应的文字printf(fopen%s\n, strerror(errno));// perror打印错误原因的字符串等同于上面strerror(errno)perror(fopen);}return 0;
}查看错误号可以通过vim打开查看
/usr/include/asm-generic/errno-base.h/usr/include/asm-generic/errno.h
4. C 库中 IO 函数工作流程
在 Linux 的世界里一切设备皆文件。我们可以系统调用 I/O 的函数Iinput输入Ooutput输出对文件进行相应的操作 open()、close()、write() 、read() 等流程如下图所示
库函数访问文件的时候根据需要设置不同类型的缓冲区从而减少了直接调用 IO 系统调用的次数提高了访问效率。
为什么需要缓冲区
内存读写熟读非常快但是写入到磁盘中非常慢在将数据从内存存储到磁盘时为了缓冲两者的读写速度设置了缓冲区。使用库函数访问文件的时候根据需要设置不同类型的缓冲区从而减少了直接调用 IO 系统调用的次数提高了访问效率。
这个过程类似于快递员给某个区域内核空间送快递一样快递员有两种方式送
来一件快递就马上送到目的地来一件送一件这样导致来回走比较频繁系统调用。等快递攒着差不多后缓冲区才一次性送到目的地库函数调用。
5. 文件描述符
[[一、Linux 下进程概述| 前置知识之进程与虚拟地址空间]]
打开现存文件或新建文件时系统内核会返回一个文件描述符文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号文件描述符是非负整数是文件的标识操作这个文件描述符相当于操作这个描述符所指定的文件。
程序运行起来后每个进程都有虚拟内存空间虚拟内存空间的 PCBPCB 可以理解为一个非常复杂的结构体中有一张文件描述符表数组默认大小1024专门用于存放文件描述符每个文件描述符都可以定位一个文件。
文件描述符表中前三个是被默认占用的分别是标准输入、标准输出、和标准错误对应的文件描述符 0、1、2 他们默认都是打开的状态且都对应了一个文件即当前终端一个设备文件所以也可以看出不同的文件描述符可以对应这同一个文件比如对于某一个文件a.txt可以调用多次 fopen() 函数打开该文件多次调用产生的文件描述符的值都是不同的。
#define STDIN_FILENO 0 //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符在程序运行起来后打开其他文件时系统会返回文件描述符表中最小可用的文件描述符并将此文件描述符记录在表中。文件描述符表位于 PCB 进程。
最大打开的文件个数Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT 即1024个文件故当文件不再使用时应及时调用 close() 函数关闭文件释放文件描述符。
查看当前系统允许打开最大文件个数 cat /proc/sys/fs/file-max查看当前默认设置最大打开文件个数 ulimit -a ---- “open files”修改默认设置最大打开文件个数为 4096ulimit -n 4096
6. 常用文件 IO 函数
此处文件 IO 函数是 Linux下系统函数标准 c 库例如 fopen() 等文件 IO 函数底层就是封装了此处的文件 IO 函数。
Linux 中调用此处的文件 IO 函数Windows 中则调用 windows 平台的文件 IO 函数所以标准 c 库的函数封装了这些系统函数更加高级效率也更高带有缓冲区还可以跨平台平常建议使用标准 c 库的文件 IO 函数。
6.1 open 函数
#include sys/types.h
#include sys/stat.h
#include fcntl.h// 打开一个已经存在的文件
int open(const char *pathname, int flags);// 创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);功能打开文件如果文件不存在则可以选择创建。
参数pathname文件的路径及文件名flags打开文件的行为标志必选项O_RDONLY, O_WRONLY, O_RDWRmode这个参数只有在文件不存在时有效即新建文件时指定文件的权限
返回值成功成功返回打开的文件描述符失败-1, 并设置 errno1flags 详细说明 必选项以下三个选项是互斥的即每次智能选择一个
取值含义O_RDONLY以只读的方式打开O_WRONLY以只写的方式打开O_RDWR以可读、可写的方式打开
可选项可以和必选项按位或联合起来使用
取值含义O_CREAT文件不存在则创建文件使用此选项时需使用mode说明文件的权限O_EXCL如果同时指定了O_CREAT且文件已经存在则出错O_TRUNC如果文件存在则清空文件内容O_APPEND写文件时数据添加到文件末尾O_NONBLOCK对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O
2mode 补充说明 mode 是一个八进制的数表示创建出的新的文件的操作权限比如0775
文件最终权限mode ~umaskumask 的作用是抹去某些权限让文件或者目录的权限更加合理一些shell 进程的 umask 掩码可以用 umask 命令查看。 不同用户 umask 值是不同的umask mode设置掩码例如umask 022这种设置方法只在当前终端有效。umask -S查看各组用户的默认操作权限
取值八进制含义S_IRWXU00700文件所有者的读、写、可执行权限S_IRUSR00400文件所有者的读权限S_IWUSR00200文件所有者的写权限S_IXUSR00100文件所有者的可执行权限S_IRWXG00070文件所有者同组用户的读、写、可执行权限S_IRGRP00040文件所有者同组用户的读权限S_IWGRP00020文件所有者同组用户的写权限S_IXGRP00010文件所有者同组用户的可执行权限S_IRWXO00007其他组用户的读、写、可执行权限S_IROTH00004其他组用户的读权限S_IWOTH00002其他组用户的写权限S_IXOTH00001其他组用户的可执行权限
#include fcntl.h
#include stdio.h
#include sys/stat.h
#include sys/types.hint main(void) {int fd -1;// 1.以只读方式打开一个文件 如果文件不存在就报错// fd open(txt, O_RDONLY);// 2.以只写的方式打开一个文件 如果文件不存在就报错// fd open(txt, O_WRONLY);// 3.以只写的方式打开一个文件 如果文件不存在就创建, 如果文件存在就直接打开// fd open(txt, O_WRONLY | O_CREAT, 0644);// 4.以只读的方式打开一个文件 如果文件不存在就创建// fd open(txt, O_RDONLY | O_CREAT, 0644);// 5.以读写的方式打开文件 如果文件存在就报错, 如果文件不存在就创建// fd open(txt, O_RDWR | O_CREAT | O_EXCL, 0644);// 6.以读写的方式打开一个文件 如果文件不存在就创建 如果文件存在就清零fd open(txt, O_RDWR | O_CREAT | O_TRUNC, 0644);if (-1 fd) {perror(open);return 1;}printf(打开文件成功....\n);return 0;}6.2 close 函数
#include unistd.h
int close(int fd);
功能关闭已打开的文件
参数fd : 文件描述符open()的返回值
返回值成功0失败 -1, 并设置 errno需要说明的是当一个进程终止时内核对该进程所有尚未关闭的文件描述符调用 close 关闭所以即使用户程序不调用 close进程在终止时内核也会自动关闭它打开的所有文件。
但是对于一个长年累月运行的程序比如网络服务器打开的文件描述符一定要记得关闭否则随着打开的文件越来越多会占用大量文件描述符和系统资源Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT 即1024个文件故当文件不再使用时应及时调用 close() 函数关闭文件释放文件描述符。
6.3 write 函数
#include unistd.h
ssize_t write(int fd, const void *buf, size_t count);
功能把指定数目的数据写到文件fd
参数fd : 文件描述符buf : 数据首地址一般是数组count : 写入数据的长度字节
返回值成功实际写入数据的字节个数失败 - 1, 并设置 errno#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main(void) {int fd -1;int ret -1;//1. 打开文件//fd open(txt, O_WRONLY | O_CREAT | O_TRUNC, 0644);//fd open(txt, O_WRONLY | O_CREAT , 0644);fd open(txt, O_WRONLY | O_CREAT | O_APPEND, 0644);if (-1 fd) {perror(open); }printf(fd %d\n, fd);//2. 写文件ret write(fd, ABCDEFG, 7); if (ret 7) {perror(write); }//3. 关闭文件close(fd);return 0;
}注意上面中 if (-1 fd) 的写法推荐 -1 写在前面因为如果少写一个等于号将 fd 赋值给-1会报错但是如果将 -1 写在后面少写一个等于号将 -1 赋值给 fd 不会报错并且永远为真这种错误排查难度大。
6.4 read 函数
#include unistd.h
ssize_t read(int fd, void *buf, size_t count);
功能把指定数目的数据读到内存缓冲区
参数fd : 文件描述符buf : 内存首地址一般是数组是一个传出参数count : 读取的字节个数
返回值成功实际读取到的字节个数失败 - 1, 并设置 errno阻塞和非阻塞的概念
读 读常规文件是不会阻塞的不管读多少字节read 一定会在有限的时间内返回。从终端设备或网络读则不一定如果从终端输入的数据没有换行符调用 read 读终端设备就会阻塞如果网络上没有接收到数据包调用 read 从网络读就会阻塞至于会阻塞多长时间也是不确定的如果一直没有数据到达就一直阻塞在那里。 写同样写常规文件是不会阻塞的而向终端设备或网络写则不一定。 【注意】阻塞与非阻塞是对于文件而言的而不是指 read、write 等的属性。
以非阻塞方式打开文件程序示例
#include unistd.h //read
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdio.h
#include errno.h //EAGAIN
int main() {// /dev/tty -- 当前终端设备// 以不阻塞方式(O_NONBLOCK)打开终端设备int fd open(/dev/tty, O_RDONLY | O_NONBLOCK);char buf[10];int n;n read(fd, buf, sizeof(buf));if (n 0) {// 如果为非阻塞但是没有数据可读此时全局变量 errno 被设置为 EAGAINif (errno ! EAGAIN) {perror(read /dev/tty);return -1;}printf(没有数据\n);}return 0;
}6.5 lseek 函数
所有打开的文件都有一个当前文件偏移量current file offset以下简称为 cfo。cfo 通常是一个非负整数用于表明文件开始处到文件当前位置的字节数。
读写操作通常开始于 cfo并且使 cfo 增大增量为读写的字节数。文件被打开时cfo 会被初始化为 0除非使用了 O_APPEND 。
#include sys/types.h
#include unistd.h
off_t lseek(int fd, off_t offset, int whence);
功能改变文件的偏移量常见作用1、移动文件指针到头文件lseek(fd, 0, SEEK_SET);2.获取当前文件指针的位置lseek(fd, 0, SEEK_CUR);3.获取文件长度lseek(fd, 0, SEEK_END);4.拓展文件的长度比如当前文件为 10字节, 需要拓展到110字节lseek(fd, 100, SEEK_END)注意需要写一次数据才能实现拓展参数fd文件描述符offset根据 whence 来移动的位移数偏移量可以是正数如果正数则相对于 whence 往右移动也可以负数如果是负数则相对于 whence 往左移动。如果向前移动的字节数超过了文件开头则出错返回如果向后移动的字节数超过文件末尾再次写入时将增大文件尺寸。whence其取值如下SEEK_SET从文件开头移动 offset 个字节SEEK_CUR从当前位置移动 offset 个字节SEEK_END从文件末尾移动 offset 个字节
返回值若 lseek 成功执行, 则返回新的偏移量如果失败 返回-1, 并设置 errno// 扩展文件的长度
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include stdio.hint main() {int fd open(hello.txt, O_RDWR);if(fd -1) {perror(open);return -1;}// 扩展文件的长度int ret lseek(fd, 100, SEEK_END);if(ret -1) {perror(lseek);return -1;}// 写入一个空数据// 注意需要写一次数据才能实现拓展否则文件大小依旧不会拓展write(fd, , 1);// 关闭文件close(fd);return 0;
}7. 文件操作相关函数
7.1 stat 函数
# 查看文件信息命令
dengitcast:~/share/4th$ stat txt#include sys/types.h
#include sys/stat.h
#include unistd.hint stat(const char *path, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
功能获取文件状态信息即获取文件信息比较 stat 和 lstat 的区别当文件是一个符号链接时lstat 返回的是该符号链接本身的信息而 stat 返回的是该链接指向的文件的信息。
参数path文件名buf保存文件信息的结构体
返回值成功 0失败: -1, 并设置 errnostruct stat结构体说明
struct stat {dev_t st_dev; //文件的设备编号ino_t st_ino; //节点mode_t st_mode; //文件的类型和存取的权限nlink_t st_nlink; //连到该文件的硬连接数刚建立的文件值为1uid_t st_uid; //用户IDgid_t st_gid; //组IDdev_t st_rdev; //设备文件的设备编号off_t st_size; //文件字节数(文件大小)blksize_t st_blksize;//块大小(文件系统的I/O 缓冲区大小)blkcnt_t st_blocks; //块数time_t st_atime; //最后一次访问时间time_t st_mtime; //最后一次修改时间time_t st_ctime; //最后一次改变时间(指属性)
};st_mode16位整数参数说明
文件类型判断应使用宏函数
测试程序1
#include stdio.h
#include string.h
#include stdlib.h
#include sys/types.h
#include sys/stat.h
#include unistd.h//获取文件相关信息
int main(void) {int ret -1;struct stat buf;memset(buf, 0, sizeof(buf));//获取文件相关信息ret stat(txt, buf);if (-1 ret) {perror(stat); return 1;}printf(st_dev: %lu\n, buf.st_dev);printf(st_ino: %lu\n, buf.st_ino);printf(st_nlink: %lu\n, buf.st_nlink);printf(st_uid: %d\n, buf.st_uid);printf(st_gid: %d\n, buf.st_gid);printf(st_rdev:%lu\n, buf.st_rdev);printf(st_size: %ld\n, buf.st_size);printf(st_blksize: %ld\n, buf.st_blksize);printf(st_blocks: %ld\n, buf.st_blocks);return 0;
}测试程序2
#include stdio.h
#include string.h
#include stdlib.h
#include sys/types.h
#include sys/stat.h
#include unistd.h//获取文件相关信息
int main(int argc, char **argv) {int ret -1;struct stat buf;if (2 ! argc) {printf(usage: ./a.out filename\n); return 1;}memset(buf, 0, sizeof(buf));//获取文件相关信息ret stat(argv[1], buf);if (-1 ret) {perror(stat); return 1;}printf(st_dev: %lu\n, buf.st_dev);printf(st_ino: %lu\n, buf.st_ino);printf(st_nlink: %lu\n, buf.st_nlink);printf(st_uid: %d\n, buf.st_uid);printf(st_gid: %d\n, buf.st_gid);printf(st_rdev:%lu\n, buf.st_rdev);printf(st_size: %ld\n, buf.st_size);printf(st_blksize: %ld\n, buf.st_blksize);printf(st_blocks: %ld\n, buf.st_blocks);#if 0switch((buf.st_mode S_IFMT)) {case S_IFSOCK: printf(socket\n);break;case S_IFLNK : printf(symbolic link\n);break;case S_IFREG : printf(regular file\n);break;case S_IFBLK : printf(block device\n);break;case S_IFDIR : printf(directory\n);break;case S_IFCHR : printf(character device\n);break;case S_IFIFO : printf(FIFO\n);break;defalt:printf(未知类型....\n);}
#elseif (S_ISREG(buf.st_mode) ) printf(is it a regular file \n); if (S_ISDIR(buf.st_mode) ) printf(directory \n); if (S_ISCHR(buf.st_mode) ) printf(character device \n); if (S_ISBLK(buf.st_mode) ) printf(block device \n); if (S_ISFIFO(buf.st_mode)) printf(FIFO (named pipe) \n); if (S_ISLNK(buf.st_mode) ) printf(symbolic link \n); if (S_ISSOCK(buf.st_mode)) printf(socket \n);
#endif//判断文件所属者权限if (buf.st_mode S_IRUSR)printf(r);elseprintf(-);buf.st_mode S_IWUSR ? printf(w) : printf(-);buf.st_mode S_IXUSR ? printf(x) : printf(-);//判断文件所属组权限buf.st_mode S_IRGRP ? printf(r) : printf(-);buf.st_mode S_IWGRP ? printf(w) : printf(-);buf.st_mode S_IXGRP ? printf(x) : printf(-);//判断文件其它权限buf.st_mode S_IROTH ? printf(r) : printf(-);buf.st_mode S_IWOTH ? printf(w) : printf(-);buf.st_mode S_IXOTH ? printf(x) : printf(-);printf(\n);return 0;
}7.2 access 函数
#include unistd.hint access(const char *pathname, int mode);
功能判断指定文件是否具有某种权限或者判断指定文件是否存在
参数pathname文件名mode文件权限4种权限R_OK是否有读权限W_OK是否有写权限X_OK是否有执行权限F_OK测试文件是否存在
返回值0有某种权限或者文件存在-1没有某种权限或文件不存在并设置 errno【注意】access() 函数判断权限实际上是帮助当前进程来判断指定文件是否具备某种权限。
#include unistd.h
#include stdio.hint main() {int ret access(a.txt, F_OK);if(ret -1) {perror(access);}printf(文件存在!\n);return 0;
}7.3 chmod 函数
#include sys/stat.hint chmod(const char *pathname, mode_t mode);
功能修改文件权限
参数filename文件名mode权限8进制数
返回值成功0失败-1, 并设置 errno#include sys/stat.h
#include stdio.h
int main() {int ret chmod(a.txt, 0777);if(ret -1) {perror(chmod);return -1;}return 0;
}7.4 chown 函数
#include unistd.hint chown(const char *pathname, uid_t owner, gid_t group);
功能修改文件所有者和所属组
参数pathname文件或目录名owner文件所有者id通过查看 /etc/passwd 得到所有者idgroup文件所属组id通过查看 /etc/group 得到用户组id
返回值成功0失败-1, 并设置 errno7.5 truncate 函数
#include unistd.h
#include sys/types.hint truncate(const char *path, off_t length);
功能修改文件大小
参数path文件文件名字length指定的文件大小如果比原来小, 删掉后边的部分如果比原来大, 向后拓展
返回值成功0失败-1, 并设置 errno7.6 link 函数
#include unistd.hint link(const char *oldpath, const char *newpath);
功能创建一个硬链接
参数oldpath源文件名字newpath硬链接名字
返回值成功0失败-1, 并设置 errno7.7 symlink 函数
include unistd.hint symlink(const char *target, const char *linkpath);
功能创建一个软链接
参数target源文件名字linkpath软链接名字
返回值成功0失败-1, 并设置 errno7.8 readlink 函数
#include unistd.hssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能读软连接对应的文件名不是读内容该函数只能读软链接文件
参数pathname软连接名buf存放软件对应的文件名bufsiz缓冲区大小第二个参数存放的最大字节数
返回值成功0读到buf中的字符个数失败-1, 并设置 errno7.9 unlink 函数
#include unistd.hint unlink(const char *pathname);
功能删除一个文件软硬链接文件
参数pathname删除的文件名字
返回值成功0失败-1, 并设置 errno8. 文件描述符复制
8.1 概述
dup() 和 dup2() 是两个非常有用的系统调用都是用来复制一个文件的描述符使新的文件描述符也标识旧的文件描述符所标识的文件。常用于文件重定向。
这个过程类似于现实生活中的配钥匙钥匙相当于文件描述符锁相当于文件本来一个钥匙开一把锁相当于一个文件描述符对应一个文件现在我们去配钥匙通过旧的钥匙复制了一把新的钥匙这样的话旧的钥匙和新的钥匙都能开启这把锁。
对比于 dup(), dup2() 也一样通过原来的文件描述符复制出一个新的文件描述符这样的话原来的文件描述符和新的文件描述符都指向同一个文件我们操作这两个文件描述符的任何一个都能操作它所对应的文件。
8.1 dup 函数
#include unistd.hint dup(int oldfd);
功能通过 oldfd 复制出一个新的文件描述符新的文件描述符是调用进程文件描述符表中最小可用的文件描述符最终 oldfd 和新的文件描述符都指向同一个文件。
参数oldf需要复制的文件描述符 oldfd
返回值成功新文件描述符失败 -1, 并设置 errno8.2 dup2 函数
#include unistd.hint dup2(int oldfd, int newfd);
功能通过 oldfd 复制出一个新的文件描述符 newfd如果成功newfd 和函数返回值是同一个返回值最终 oldfd 和新的文件描述符 newfd 都指向同一个文件。
参数oldfd需要复制的文件描述符newfd新的文件描述符这个描述符可以人为指定一个合法数字0 - 1023如果指定的数字已经被占用和某个文件有关联此函数会自动关闭 close() 断开这个数字和某个文件的关联再来使用这个合法数字。如果oldfd和newfd值相同相当于什么都没有做。
返回值成功返回 newfd失败返回 -1, 并设置 errno8.3 示例分析 9. fcnlt 函数
#include unistd.h
#include fcntl.hint fcntl(int fd, int cmd, ... /* arg */);
功能改变已打开的文件性质fcntl 针对描述符提供控制。
参数fd操作的文件描述符cmd操作方式arg针对cmd的值fcntl能够接受第三个参数int arg。
返回值成功返回某个其他值失败-1, 并设置 errnofcntl函数有5种功能常用第一种和第三种 复制一个现有的描述符复制的是第一个参数 fd返回值得到一个新的文件描述符cmdF_DUPFD 获得设置文件描述符标记cmdF_GETFD或F_SETFD 获得设置文件状态标记cmdF_GETFL或F_SETFL F_GETFL获取指定的文件描述符文件状态 flag获取的 flag 和我们通过 open 函数传递的 flag 是一个东西。F_SETFL设置文件描述符文件状态 flag 必选项O_RDONLY, O_WRONLY, O_RDWR 不可以被修改可选性O_APPEND表示追加数据, O_NONBLOCK设置成非阻塞可选项是可以被修改的。 获得设置异步I/O所有权cmdF_GETOWN或F_SETOWN 获得设置记录锁cmdF_GETLK, F_SETLK或F_SETLKW
参考示例
// 等价于 dup()
int new_fd fcntl(fd, F_DUPFD, 0);
// 获取文件状态标志
int flag fcntl(fd, F_GETFL, 0);
switch (flag O_ACCMODE) {case O_RDONLY:printf(read only\n);break;case O_WRONLY:printf(write only\n);break;case O_RDWR:printf(read and write\n);break;default:break;
}if (flag O_APPEND) {printf(append\n);
}flag | O_APPEND; // 追加flag
int ret fcntl(fd, F_SETFL, flag); //设置文件状态标记10. 目录相关操作
10.1 getcwd 函数
#include unistd.hchar *getcwd(char *buf, size_t size);
功能获取当前进程的工作目录
参数buf缓冲区存储当前的工作目录size缓冲区大小
返回值成功buf中保存当前进程工作目录位置失败NULL, 并设置 errno【补充】可执行程序在某个目录下执行该可执行程序的工作目录默认就是该目录。
10.2 chdir 函数
#include unistd.hint chdir(const char *path);
功能修改当前进程的工作目录
参数path切换的路径
返回值成功0失败-1, 并设置 errno【补充】可执行程序在某个目录下执行该可执行程序的工作目录默认就是该目录。
10.3 opendir 函数
#include sys/types.h
#include dirent.hDIR *opendir(const char *name);
功能打开一个目录返回一个指针指向目录目录也是一个文件
参数name目录名
返回值成功返回指向该目录结构体指针理解为目录流信息失败NULL, 并设置 errno10.4 readdir 函数
#include dirent.hstruct dirent *readdir(DIR *dirp);
功能读取目录中数据即读取目录下的文件信息
参数dirpopendir 的返回值
返回值成功目录结构体指针指针代表读取到的文件的信息失败读取到目录信息流末尾或者读取失败了返回NULL, 并设置 errno相关结构体说明
struct dirent
{ino_t d_ino; // 此目录进入点的inodeoff_t d_off; // 目录文件开头至此目录进入点的位移signed short int d_reclen; // d_name 的长度, 不包含NULL 字符unsigned char d_type; // d_type 所指的文件类型 char d_name[256]; // 文件名
};d_type 文件类型说明
取值含义DT_BLK块设备DT_CHR字符设备DT_DIR目录DT_LNK软链接DT_FIFO管道DT_REG普通文件DT_SOCK套接字DT_UNKNOWN未知
10.5 closedir 函数
#include sys/types.h
#include dirent.hint closedir(DIR *dirp);
功能关闭目录
参数dirpopendir返回的指针
返回值成功0失败-1, 并设置 errno示例读取某个目录下所有的普通文件的个数
#include sys/types.h
#include dirent.h
#include stdio.h
#include string.h
#include stdlib.hint getFileNum(const char * path);
int main(int argc, char * argv[]) {if(argc 2) {printf(%s path\n, argv[0]);return -1;}int num getFileNum(argv[1]);printf(普通文件的个数为%d\n, num);return 0;}// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {// 1.打开目录DIR * dir opendir(path);if(dir NULL) {perror(opendir);exit(0);}struct dirent *ptr;// 记录普通文件的个数int total 0;while((ptr readdir(dir)) ! NULL) {// 获取名称char * dname ptr-d_name;// 忽略掉. 和..if(strcmp(dname, .) 0 || strcmp(dname, ..) 0) {continue;}// 判断是否是普通文件还是目录if(ptr-d_type DT_DIR) {// 目录,需要继续读取这个目录char newpath[256];sprintf(newpath, %s/%s, path, dname);total getFileNum(newpath);}if(ptr-d_type DT_REG) {// 普通文件total;}}// 关闭目录closedir(dir);return total;
}10.6 mkdir 函数
#include sys/types.h
#include dirent.hint mkdir(const char *pathname, mode_t mode);
功能创建一个目录
参数pathname: 创建的目录的路径mode: 权限八进制的数
返回值成功0失败-1, 并设置 errno#include sys/stat.h
#include sys/types.h
#include stdio.h
int main() {int ret mkdir(aaa, 0777);if(ret -1) {perror(mkdir);return -1;}return 0;
}10.7 rename 函数
#include stdio.hint rename(const char *oldpath, const char *newpath);
功能把oldpath的文件名改为newpath即修改目录名
参数
oldpath旧文件名
newpath新文件名
返回值
成功0
失败-1, 并设置 errno11. 时间相关函数
11. 0 时间相关概念
谈到时间包括日期必须先明确下面的概念。
Linux 系统一直使用两种不同的时间值日历时间和进程时间 1、日历时间 日历时间是从国际标准时间公元 1970 年 1 月1日 000000 到现在所经历的秒数UTC此时间精度为秒。系统用 time_t 保存这种时间值如果时间要求不是非常精确则可以使用此时间例如记录文件修改的时间。time_t 是个长整型数值其定义为typedef long time_t;其实就是一个长整数。
日历时间还要注意时区问题由于世界各国家与地区经度不同地方时也有所不同因此将其划分为 24 个时区其中 UTC Coordinated Universal Time世界标准时间以前称为格林威治时间(GMT)为 0 时区。所以同样都是日历时间但是时区不同时间也将不同。我们经常涉及的时区为 UTC 所在的 0 时区与东八区时区中国所在的时区两者两者相差 8 小时比如 UTC 时间是10:00, 中国时间就是18:00。
分解时间broken-down time就是将时间按照年、月、日、时、分、秒等格式显示的时间。如Wed Apr 1 11:23:33 2020。【注意】**它与时区相关时区不同时间也将不同。
简单日历时间simple calendar time就是从固定时间点1970年1月1日-00.00.00UTC到当前时间点所经过的秒数。计算机在计时时就使用它。【注意】它与时区无关。固定为 0 区UTC。
【注意】日历时间存在时区问题简单日历时间不存在时区问题简单日历时间相当于 UTC 时间。
2、进程时间 也被称为 CPU 时间用以度量进程使用的中央处理机资源。进程时间以时钟滴答计算历史上曾经以每秒钟为50、60或100个滴答使用 sysconf() 函数可以得到每秒钟的滴答数。系统用 clock_t 保存这种时间值其定义为typedef long clock_t;其实就是一个长整数。
当度量一个进程的执行时间时UNIX系统使用三个进程时间值
时钟时间时钟时间又称为墙上时钟时间wall clock time。它是进程运行的时间总量其值与系统中同时运行的进程数有关。用户CPU时间用户 CPU 时间是执行用户指令所用的时间。系统CPU时间系统 CPU 时间是为该进程执行内核程序所经历的时间。例如每当一个进程执行一个系统服务时例如 read() 或 write()则在内核中执行该服务所花费的时间就计入该进程的系统CPU时间。用户 CPU 时间和系统 CPU 时间之和常被称为 CPU 时间。
11.1 时间获取函数
11.1.1 time 函数
#include time.htime_t time(time_t * timer);
功能获取机器操作系统当前的时间返回的结果是一个 time_t精确到秒。
参数timerNULL 时得到机器操作系统当前的日历时间 timer时间数值时用于设置日历时间
返回值成功 返回机器操作系统当前的日历时间失败-1, 并设置 errno11.1.2 clock 函数
#include time.hclock_t clock(void);
功能获取从程序启动到此函数调用所消耗的处理时间精确到毫秒。
参数空
返回值成功返回消耗的时间失败-1, 并设置 errno【注意】也获取程序所使用的秒数除以 CLOCKS_PER_SEC 即可。
11.1.3 gettimeofday() 函数
#includesys/time.h
#includeunistd.hint gettimeofday(struct timeval *tv, struct timezone *tz);
功能获取机器操作系统当前的时间存于 tv 结构体中相应的时区信息则存于 tz 结构体中。可以精确到微秒。参数tv存放机器操作系统当前的日历时间。tz存放当前时区通常设置为 NULL。
返回值成功返回 0失败返回 -1, 并设置 errnostruct timevalstruct timezone 结构体
struct timeval { time_t tv_sec; /* seconds (秒)*/suseconds_t tv_usec; /* microseconds微秒 */};struct timezone {int tz_minuteswest; /* minutes west of Greenwich */int tz_dsttime; /* type of DST correction */
};int tz_minuteswest; /* 格林威治时间往西方的时差 */
int tz_dsttime; /* 时间的修正方式*/11.2 日历时间转换为分解时间
11.2.1 localtime() 函数
#include time.hstruct tm *localtime(const time_t *timep);
功能将 time_t 所表示的日历时间转换为本地时间我们是东八区并转成 tm 类型。
参数timep 为日历时间一般通过 time() 函数获取。
返回值以 tm 结构表达的时间struct tm 类型的各数据成员分别表示年月日时分秒。
struct tm {int tm_sec; // 代表目前秒数正常范围为0-59但允许至61秒int tm_min; // 代表目前分数范围0-59int tm_hour; // 从午夜算起的时数范围为0-23int tm_mday; // 目前月份的日数范围01-31int tm_mon; // 代表目前月份从一月算起范围从0-11int tm_year; // 从1900 年算起至今的年数int tm_wday; // 一星期的日数从星期一算起范围为0-6int tm_yday; // 从今年1月1日算起至今的天数范围为0-365int tm_isdst; // 日光节约时间的旗标
}; 【注意】与下文 gmtime() 函数区分localtime() 获得的是当地时区的分解时间。
11.2.2 gmtime() 函数
#include time.hstruct tm *gmtime(const time_t *timep);
功能将 time_t 结构所表示的日历时间转换成转成 struct tm 类型然后将 tm 返回。
参数timep 是由 time(NULL) 得到的日历时间
返回值返回 tm 格式的简单日历时间UTC 时间。【注意】localtime() 和 gmtime() 区别
gmtime() 获得是 0 时区即UTC时间localtime() 获得当地时区我们获得的是东八区时间 两者两者相差 8 小时比如 gmtime() 是10:00 中国时间 localtime() 就是18:00。
11.3 分解时间转换为日历时间
11.3.1 mktime() 函数
#include time.htime_t mktime(struct tm *tm);
功能将 tm 格式的时间转化为 time_t即经历的秒数。
参数tm 格式的时间。
返回值成功机器操作系统当前的日历时间。失败返回 -1, 并设置 errno【注意】此函数使用的时区为机器操作系统默认的时区。
11.4 将时间转换为字符串相关函数
11.4.1 asctime() 函数
#include time.hchar *asctime(struct tm *ptr);
功能将 tm 结构中的信息转换成真实世界所使用的时间日期表示方法并以字符串形态返回。即将分解时间转换成字符串。参数ptr 为 struct tm 类型的时间结构体
返回值返回的时间字符串格式为星期,月,日,小时分秒,年示例
#include stdio.h
#include time.hint main() {time_t timer;struct tm* tblock;timer time(NULL);tblock localtime(timer);printf(Local time is: %s, asctime(tblock));return 0;
}11.4.2 ctime() 函数
#include time.hchar *ctime(const time_t *timep);
功能将 time_t 结构中的信息转换成真实世界所使用的时间日期表示方法并以字符串形态返回。即将日历时间转换为分解时间。
参数timep 是由 time(NULL) 得到的日历时间
返回值返回的时间字符串格式为星期,月,日,小时分秒,年【注意】 若再调用相关的时间日期函数此字符串可能会被破坏。
11.4.3 strftime() 与 strptime() 函数
#include time.hsize_t strftime(char *s, size_t maxsize, char *format, const struct tm *timeptr);
char *strptime(const char *buf, const char*format, struct tm *timeptr)
功能这两个函数都是时间格式控制函数在功能上看起来正好相反。strftime 将一个 tm 结构格式化为一个字符串strptime 则是将一个字符串格式化为一个 tm 结构。参数s存放格式化的字符串maxsize最多可以输出的字符串数format格式化timeptr要转换的 tm 格式时间
返回值strftime 返回 UTC 时间秒数strptime() 返回一个指针这个指针指向最后一个被转换内容的后面一个字符处。【注意 】strptime() 如果它遇到无法转换的字符转换就会简单的在那里停止。调用程序需要充分的检查已经转换的字符串以确保这些有效的转换值已经写进 tm 结构体中。
11.5. 时间差计算函数
11.5.1 difftime() 函数
#include time.hdouble difftime(time_t time1, time_t time0);
功能计算两个时间的差值。
参数两个 time_t 格式的时间
返回值时间差精确到秒11.6 线程安全的时间转换函数
以下时间转换函数是线程安全的多线程中应用对应的 xxx_r 函数代替 xxx 函数
// asctime_r: 将 tm 转换为字符串形式
char *asctime_r(const struct tm *tm, char *buf);// ctime_r: 将 time_t 时间转换为字符串形式
char *ctime_r(const time_t *timep, char *buf);// gmtime_r: 将 time_t 时间转换为 tm 格式时间
struct tm *gmtime_r(const time_t *timep, struct tm *result);// localtime_r: 将 time_t 时间转换为 tm 格式
struct tm *localtime_r(const time_t *timep, struct tm *result);