国际域名的外贸网站,太原那有网站设计公司,外贸买家网站,岳阳网站建设一站式服务裸奔层次#xff1a;不带操作系统的编程 APP(应用程序) -------------------------------- Hardware(硬件) 特点#xff1a;简单#xff0c;应用程序直接操作硬件(寄存器) 缺点#xff1a; 1. 搞应用开发的必须要了解硬件的实现细节#xff0c;能够看懂原理图… 裸奔层次不带操作系统的编程 APP(应用程序) -------------------------------- Hardware(硬件) 特点简单应用程序直接操作硬件(寄存器) 缺点 1. 搞应用开发的必须要了解硬件的实现细节能够看懂原理图 2. 无并发不能同时运行多个程序单任务 带OS的编程 APP(应用程序) --------------------------------------------------- OS(操作系统)通过驱动控制硬件 --------------------------------------------------- Hardware(硬件) 特点 1. 应用开发可以把精力放到应用的业务逻辑上而不需要关心硬件的具体实现细节 2. 提供并发功能允许同时运行多个应用多任务 OS Operating System 操作系统是管理和分配系统软硬件资源的软件系统 linux操作系统下进行应用开发就是调用操作系统的API函数去操作具体的硬件或者说是使用linux提供的服务 如 open close read write ...... linux是一个free(开源)的操作系统 设计理念Everything is a file in Linux / Unix(一切皆文件) 在linux下面操作任何东西其实都是在操作文件或者说在linux下面操作任何东西都是通过文件的接口去操作的 在linux下面一切都是文件(鼠标键盘触摸屏......) 1. 文件IO是什么 文件IO分为系统IO和标准IO IOinput output 对文件的输入和输出操作的基本函数接口 Linux有一个设计思想Everything is a file in Linux (一切皆文件) 文件系统是用来存储组织和管理文件的一套方法和规则(NTFSfat32exfat......) 存储文件一般分为两个部分 文件的属性inode唯一标识一个文件的存在与否(文件名文件类型文件大小...) 文件本身的内容(用户数据) 如果一个文件存在可以没有内容但是必须有属性 Linux中到底如何组织和存放文件的呢? 大概步骤: 硬件 inode (属性)------文件的内容 linux内核中 struct inode{} 如果没有创建这个结构体则说明系统不识别这个硬件 用来描述一个文件的物理inode信息系统识别到一个文件的存在就会为它创建一个struct inode的结构体一个文件只会唯一的对应一个struct inode 如果打开了某一个文件 使用struct file的结构体表示这个打开的文件 struct file{}用来描述一个已经打开的文件 文件状态标记 (如O_RDONLYO_WRONLY......) 文件的偏移量 / offset (类似光标) struct inode * 每一个打开的文件都会对应一个struct file 一个文件可以同时被多个进程打开一个进程也可以同时打开多个文件 一个进程同时打开了多个文件意味着需要保存每一个打开的文件的struct file 使用一个数组保存了所有struct file结构体的地址 (结构体指针数组) linux为了屏蔽文件操作的具体细节会为每一个进程创建一个进程文件表项保存每一个进程打开的文件 struct file * 数组 0 struct file * ---struct file --- struct inode...... 1 struct file * ---struct file --- struct inode...... 2 struct file * ---struct file --- struct inode...... 3 struct file * ---struct file --- struct inode...... 4 ...... 5 ...... linux提供操作文件的函数接口 fd open() 打开一个指定的文件返回进程文件表项的下标 int 文件描述符在linux应用中用来描述一个已经打开的文件每一个打开的文件都有一个唯一的id后续操作这个文件都是通过该文件描述符去操作的 read(fd...) write(fd...) close(fd...) ...... 对于用户来说我们操作文件的时候只需要知道数组的下标就可以去操作这个文件这个下标在用户的眼中叫做文件描述符 操作文件的内部流程 数字 (文件描述符) ------- 进程文件表项的内容 (结构体指针数组) ------- struct file ------- struct inde ------- 硬件上面的inode (物理inode) ------- 文件本身的内容 为了方便linux把上面所有的流程都封装起来了用户不需要知道具体的操作细节 只需要调用OS提供给我们的API函数接口就可以了 Linux系统提供的这些用于操作文件的接口函数(如:open/read/write...) 我们称之为系统IO 系统IO操作系统提供给用户操作文件的接口!!! 二. Linux中一些具体的API函数接口(系统接口) 对文件的操作步骤 1. 打开文件 open 2. 对文件的操作(读写....) 3. 关闭文件 close 注意 1. 对文件的操作接口尽量不要放到共享文件夹因为共享文件夹是windows的文件系统(有可能不兼容) 2. 系统IO提供的API函数接口有很多只是学习了其中一小部分(注重方法和基础的API) (1) 打开文件 (open) NAMEopen, openat, creat - open and possibly create a file打开或者创建(创建并且打开)一个文件
SYNOPSIS#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要打开或者创建的文件名,带路径(如果不写路径,默认就是程序的当前路径)如: /home/china/1.txt or 1.txtflags打开文件的标记(使用位域实现,可以添加多个标记)O_RDWR read and write 读写打开O_RDONLY read only 只读打开O_WRONLY write only 只写打开以上的三个标记只能选一个文件的打开方式O_APPEND追加标记,打开文件的时候,文件的偏移量(光标)在文件的末尾默认情况下文件的偏移量在文件开头偏移量可以看做是文件的光标(读和写的位置)O_CREAT创建标记,如果文件不存在,则创建这个文件O_EXCL 和O_CREAT配合使用,用来测试文件是否存在同时指定这两个标记,文件存在则会报错,并且errno被设置为EEXIST,文件不存在则创建O_TRUNC truncate截短,在文件打开的时候,把文件的内容清空O_NONBLOCK以非阻塞的方式打开文件,文件的默认打开方式是阻塞打开的阻塞:等待如果文件暂时没有内容可读,read这个文件就会等待(直到有数据可读或出错)如果文件暂时没有空间,write这个文件就会等待(直到有空间或出错)非阻塞:不等待如果文件暂时没有内容可读,read这个文件就不会等待,直接返回一个错误如果文件暂时没有空间,write这个文件不会等待,直接返回一个错误....多个标记可以使用 | 连接(Linux中的标志大部分都是使用位域实现的,一个整数的某些bit有特殊的含义)例如O_RDWR | O_CREAT | O_TRUNC 以读写的方式打开,文件不存在则创建,文件存在则清空内容mode指定文件的权限,当第二个参数中带有O_CREAT时,必须指定创建的文件的权限,有两种指定方式:1.使用OS定义的宏标识权限S_IRWXU 00700 user (file owner) has read, write, and execute permission用户拥有可读可写可执行的权限S_IRUSR 00400 user has read permissionS_IWUSR 00200 user has write permissionS_IXUSR 00100 user has execute permissionS_IRWXG 00070 group has read, write, and execute permissionS_IRGRP 00040 group has read permissionS_IWGRP 00020 group has write permissionS_IXGRP 00010 group has execute permissionS_IRWXO 00007 others have read, write, and execute permissionS_IROTH 00004 others have read permissionS_IWOTH 00002 others have write permissionS_IXOTH 00001 others have execute permissionU/USR 用户(文件的拥有者)G/GRP 组用户O/OTH 其他用户S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH rw-r--r--06442.直接使用八进制数字标识权限0777111 111 111rwx rwx rwx 0664110 110 100rw- rw- r--返回值:打开成功返回打开的文件的文件描述符(进程文件表项的下标),是一个整数2 int 未使用中的最小值因为操作系统会为每一个进程打开三个文件标准输入文件(键盘) 文件描述符 STDIN_FILENO 0 ---有缓冲区标准输出文件(终端) 文件描述符 STDOUT_FILENO 1 ---有缓冲区标准出错文件(终端) 文件描述符 STDERR_FILENO 2 ---没有缓冲区后序操作这个文件的时候,就可以直接使用这个数字表示打开失败返回-1,同时errno被设置
----------------------------------------------------------------------------
man errno
#include errno.h
errno是一个全局变量,表示的是最后一次调用系统函数出错的错误码
vim /usr/include/errno.h
----------------------------------------------------------------------------
NAMEperror - print a system error message打印系统的错误信息
SYNOPSIS#include stdio.h void perror(const char *s);
perror可以把当前的错误码转化为对应的字符串并且打印出来
perror(用户提示性字符串);用户提示性字符串:errno转化之后的错误字符串\n ---自动换行 int creat(const char *pathname, mode_t mode);创建并且打开一个文件
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
// dirfd目录的文件描述符open(/home/china/1.txt, O_RDWR);int dirfd open(/home/china, I_RDONLY); // 以读的方式打开一个目录
openat(dirfd, 1.txt, O_RDWR); ✔2. 关闭文件 (close) NAMEclose - close a file descriptorclose是用来关闭fd指定的文件
SYNOPSIS#include unistd.hint close(int fd);fd文件描述符返回值成功返回0失败返回-1同时errno被设置 ✔3. 读写文件 (read / write) write把数据放到文件里面去 (修改文件的内容) NAMEwrite - write to a file descriptor
SYNOPSIS#include unistd.hwrite的作用是把buf指针指向的内存的前面count个字节写入到fd表示的文件中去
替换光标所在的内容ssize_t write(int fd, const void *buf, size_t count);fd你要把内容写入到哪一个文件中去(open的返回值)
buf指针,指向一段内存地址,存储了你要写入的数据为什么要使用const呢?从语义来说,在write函数的内部不应该通过buf去修改数据,为了增强程序的健壮性
count字节数量,表示你要写入多少个字节返回值:0 返回实际写入到文件中的字节数量ps 一般情况下write的返回值通常等于请求写的字节数count0 表示什么也没写入-1 表示写入失败,同时errno被设置写入的位置位于文件的光标位置ssize_t w write(1, hello,nihao, 10); // 往标准输出写入数据
// 标准输出(终端)中输出hell0,niha
// w 10; ??? #include stdio.h
#include unistd.hint main() {int w write(1, hello, 10);printf(%d\n, w);return 0;
}hello%d
10#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main() {int fd open(1.txt, O_RDWR | O_CREAT, 0777);char buf[100] hello world;ssize_t w write(fd, buf, 50);printf(w %ld\n, w);close(fd);return 0;
}// 50 vim 1.txt read从文件中把数据拿出来 NAMEread - read from a file descriptor
SYNOPSIS#include unistd.hread是从指定的文件中(fd)读取count个字节,存放到buf指向的内存空间中去
读取的位置位于文件的光标位置ssize_t read(int fd, void *buf, size_t count); fd文件描述符你要从哪一个文件中读取内容(open的返回值)
bufvoid*---通用指针指针,指向一段可用的内存空间,表示你要把读取到的数据存放到哪一个位置,不能是野指针,也不能是空指针
count字节数量,表示你要读取多少个字节返回值:0 返回实际读取到的数据数量(有可能小于count)0 表示什么也没读到-1 表示读取失败,同时errno被设置// 从标准输入中读取数据
char buf[100] {0};
ssize_t r read(0, buf, 100);
// buf[r - 1] 0;
printf(r %ld\n, r); // 回车也会读进去
printf(buf:%s\n, buf); 注意 文件的偏移量 (光标位置) 由内核自动维护一般来说打开文件的时候offset0 每一次成功的读和写都会让偏移量改变 你读/写了count个字节 offset count 在读写文件的时候要注意文件的光标所在位置 关标位置不是在开头吗 不一定多线程 ✔4. 定位文件的光标 (偏移量 lseek) NAMElseek - reposition read/write file offset
SYNOPSIS#include sys/types.h#include unistd.h定位fd表示的文件的偏移量
off_t lseek(int fd, off_t offset, int whence);fd你要定位的文件的文件描述符
offset偏移量,具体的新位置需要结合第三个参数使用
whence定义标记,有三种SEEK_SET 基于文件开头定位新位置 文件开头 offset(0)SEEK_CUR 基于文件当前位置定位新位置 文件当前光标位置 offset(可正可负)SEEK_END 基于文件结尾定位新位置 文件结尾 offset(可正可负)负往前面偏移正往后面偏移 留空洞如:定位到文件开头lseek(fd, 0, SEEK_SET);定位到文件结尾lseek(fd, 0, SEEK_END);返回值:成功返回新光标位置离文件开头的字节数量失败返回-1,同时errno被设置 也可以利用lseek计算文件大小 size lseek(fd, 0, SEEK_END); 练习利用文件IO的函数写一个程序实现两个普通文件的复制功能 ./my_cp 1.txt 2.txt #include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include stdlib.h
#include string.hint main(int argc, char *argv[]) {if (argc ! 3) {printf(USE:./mycp 1.txt 2.txt\n);return -1;}// 打开两个文件int fd1 open(argv[1], O_RDONLY);if (-1 fd1) {perror(open fd1 failed);return -1;}int fd2 open(argv[2], O_RDWR | O_TRUNC | O_CREAT, 0777);if (-1 fd2) {perror(open fd2 failed);close(fd1);return -1;}#if 0 // bug:文件有可能很大,malloc不能开那么大的空间off_t size lseek(fd1, 0, SEEK_END);printf(size %ld\n, size);lseek(fd1, 0, SEEK_SET);char *buf malloc(sizeof(char) * size);memset(buf, 0, size);read(fd1, buf, size);write(fd2, buf, size);free(buf);
#endif// 不断的读取1.txt的内容,把读取到的内容写入到2.txtchar buf[50] {0};while (1) {ssize_t r read(fd1, buf, 50);if (0 r) {// 读取完毕break;} else if (-1 r) {perror(read 1.txt failed);break;}ssize_t w write(fd2, buf, r);if (-1 w) {perror(write 2.txt failed);break;}}// 关闭两个文件close(fd1);close(fd2);return 0;
} 总结 1. 理解裸奔层次和linux系统层次的区别 2. linux文件操作的大致流程 3. 文件描述符是什么 文件描述符是linux系统提供给应用为了唯一标识一个已经打开的文件是一个0的整数 4. 系统IO是什么 linux系统提供给应用程序用来对文件input / output 操作的函数接口 5. 函数的用法不一定要记得用的时候问man ✔5. 设置文件的掩码 (umask) umask 表示创建文件时权限的掩码 创建文件时不能指定umask中值为1的bit umask ------0002 000 000 010 在创建文件的时候不能指定umask中值为1的bit(指定了也会忽略) mode mode (~umask) umask: 0002 000 000 010 mode: 0777 111 111 111 ~umask: 0002 111 111 101 ------------------------------- 111 111 101 默认情况下组用户和其他用户的写权限是不能指定的 (0022) 可以通过命令或者函数修改: 这种方式并不能永久改变 umask 值只是改变了当前会话的 umask 值打开一个新的 terminal 输入 umask 命令可以看到 umask 值仍是默认的 002。要想永久改变 umask 值则可以修改文件 /etc/bashrc在文件中添加一行 umask 022 命令: umask 新的掩码 函数: NAMEumask - set file mode creation mask
SYNOPSIS#include sys/types.h#include sys/stat.hmode_t umask(mode_t mask);mask:你要指定的新的文件掩码返回值:返回上一次的文件掩码设置之前的文件掩码mode_t32位的无符号整数类型 ✔6. 获取和修改程序的当前工作路径 在linux中任意一个程序都有一个工作路径 工作路径在哪一个目录里面运行这个程序这个程序的工作路径就在哪里 (不管你的程序存储在哪一个位置) 如: 你有 /home/china/123/abc/a.out 当前在: /home/china/123/abc 运行a.out 运行命令: ./a.out 工作路径: /home/china/123/abc 当前在: /home/china 运行a.out 运行命令: ./123/abc/a.out 工作路径: /home/china 有什么意义呢? 你如果在a.out中写了 int fd open(1.txt, O_RDWR | O_CREAT, 0664); 这里的 1.txt 是相对路径此相对路径是相对于工作路径而言的 如果你的工作路径是: /home/china/123/abc 就会去/home/china/123/abc找1.txt 如果你的工作路径是: /home/china 就会去/home/china找1.txt 如何获取当前工作路径 NAME getcwd, getwd, get_current_dir_name - get current working directory
SYNOPSIS#include unistd.h把获取到的工作路径(绝对路径)保存到buf指向的内存空间(必须可用)
char* getwd(char *buf);buf是用来保存获取到的工作路径的返回值:成功返回获取到的工作路径字符串的首地址(buf)失败返回NULL,同时errno被设置警告 the getwd function is dangerous and should not be used.getwd有一个bug,不应该被使用,可能会造成内存越界如果buf指向的空间不够大(当前目录字符串长度超出了buf指向的空间)getwd就会访问buf后面的空间(造成数据会误修改)
---------------------------------------------------------------------------
getcwd是getwd的升级版本
char* getcwd(char *buf, size_t size);buf是用来保存获取到的工作路径的
size指定了buf可用空间的大小,如果当前的目录长度字符串超过了size-1,这个函数就会报错返回值:成功返回获取到的工作路径字符串的首地址(buf)失败返回NULL,同时errno被设置
---------------------------------------------------------------------------
get_current_dir_name也是获取当前的工作路径,只不过这个函数不需要你给定空间,
会在函数内部malloc自动分配足够长的空间,保存获取到的工作路径,并且返回首地址
所以为了防止内存泄漏,使用者使用完毕之后,应该free这个空间
char* get_current_dir_name(void);返回值:成功返回获取到的工作路径字符串的首地址失败返回NULL,同时errno被设置 修改: 改变进程当前的工作路径 NAMEchdir, fchdir - change working directory
SYNOPSIS#include unistd.hint chdir(const char *path);path要切换到的工作路径的目录字符串
chdir(/home/china/);
-----------------------------------------------------
int fchdir(int fd);fd要切换到的工作目录的文件描述符如
int fd open(/home/china/, O_RDONLY) ;
fchdir(fd);返回值:成功会改变当前的工作路径,返回0失败返回-1,同时errno被设置 ✔7. 文件截短 (truncate) NAMEtruncate, ftruncate - truncate a file to a specified length截短一个文件到指定的长度
SYNOPSIS#include unistd.h#include sys/types.hint truncate(const char *path, off_t length);文件必须可写path你要截短的文件的路径名(相对路径/绝对路径)绝对路径------工作路径pathlength截短之后的文件长度length 原来的长度截短文件变成指定的长度文件的大小改变length 原来的长度 留空洞空洞的内容是无知的返回值:成功返回0失败返回-1,同时errno被设置int ftruncate(int fd, off_t length);int fd open(...); // 文件必须以可写的方式打开
ftruncate(fd, length); ✔8. 删除文件 rm 删除文件
rmdir 删除空目录unlink // 删除一个普通文件
rmdir // 删除一个空目录
remove // 删除一个普通文件或者一个空目录NAMEunlink, unlinkat - delete a name and possibly the file it refers to
SYNOPSIS#include unistd.hint unlink(const char *pathname);
// 删除一个文件的时候仅仅只是标记inode没有被使用了pathname要删除的那个文件的文件名带路径返回值成功删除返回0失败返回-1同时errno被设置我们知道Linux中文件是用inode节点来区分文件的当我们删除一个文件的时候并不一定
系统就会释放inode节点的内容。当满足下面的要求的时候系统才会释放inode节点的内容(1) inode中记录指向该节点的硬链接数为0(2) 没有进程打开指向该节点的文件执行unlink()函数并不一定会真正的删除文件如果要删除的文件是硬链接文件它先会检查
文件系统中此文件的硬链接数是否为1如果不是1说明此文件还有其他硬链接对象删除一个
文件的时候仅仅只是标记inode没有被使用了只对此文件的硬链接数进行减1操作。若硬链接
数为1并且在此时没有任何进程打开该文件此内容才会真正地被删除掉。在有进程打开
此文件的情况下则暂时不会删除直到所有打开该文件的进程都结束时文件就会被删除
如果要删除的文件是符号链接软链接文件则此链接会被删除
--------------------------------------------------------------------------------------
NAMErmdir - delete a directory
SYNOPSIS#include unistd.hint rmdir(const char *pathname);
目录必须是空的pathname:要删除的目录的路径名返回值成功删除返回0失败返回-1同时errno被设置
------------------------------------
NAMEremove - remove a file or directory
SYNOPSIS#include stdio.hint remove(const char *pathname);
删除一个普通文件或者一个空目录remove 删除一个普通文件 ------unlink
remove 删除一个空目录 ------rmdir 9. 获取文件的属性 (stat) 任何一个文件都有自己的属性 (inode中的内容) man -a inode NAMEstat, fstat, lstat, fstatat - get file status获取文件属性
SYNOPSIS#include sys/types.h#include sys/stat.h#include unistd.hstat是用来获取pathname指定的文件的属性信息,获取到的属性信息保存到statbuf指针指向
的内存中statbuf必须指向一个可用的空间
stat这个函数获取文件的属性信息只需要提供文件名不需要打开它
int stat(const char *pathname, struct stat *statbuf);pathname你要获取哪一个文件的属性(路径名)
statbuf结构体指针,指向一块可用的空间,用来保存获取到的文件的属性信息返回值:成功返回0,失败返回-1,同时errno被设置struct stat *statbuf NULL;
int r stat(1.txt, statbuf); // ERROR 指针必须指向一块可用的空间struct stat statbuf;
// struct stat *p malloc(sizeof(*p));
int r stat(1.txt, statbuf);
---------------------------------------------------------------------------
fstat功能和stat类似,只不过需要提供文件描述符,需要提前打开文件
int fstat(int fd, struct stat *statbuf);
---------------------------------------------------------------------------
lstat功能和stat类似,只不过当pathname是一个符号链接的时候,
lstat获取的是符号链接本身的属性信息(软连接同样有自己的inode),
而stat是获取符号链接指向的那个文件的属性信息
int lstat(const char *pathname, struct stat *statbuf);文件B是文件A的符号链接(ln -s A B)给A创建一个软连接BB-----A stat(B) 获取的是A的inode的信息lstat(B) 获取的是B的inode的信息 文件的属性 实际上Linux系统是使用一个 struct stat 的结构体保存文件的所有属性信息 struct stat {dev_t st_dev; /* ID of device containing file */// 容纳该文件的设备的设备号码文件存储在哪一个设备上面ino_t st_ino; /* Inode number */// 该文件的inode号码mode_t st_mode; /* File type and mode */// 保存了文件的权限和类型使用位域bit实现nlink_t st_nlink; /* Number of hard links */// 硬链接数量有多少个文件名关联这个inodeuid_t st_uid; /* User ID of owner */// 文件的所有者ID文件属于哪个用户gid_t st_gid; /* Group ID of owner */// 文件的组ID文件属于哪个用户组dev_t st_rdev; /* Device ID (if special file) */// 如果文件是一个特殊的设备,设备号码off_t st_size; /* Total size, in bytes */// 文件的大小(字节数量)// 普通文件文件内容的大小// 对应符号链接软链接的文件内容是什么呢// 指向的那个文件的文件名字// 目录文件的内存是什么// 是目录项大小为4096blksize_t st_blksize; /* Block size for filesystem I/O */块大小与具体的硬件设备有关blkcnt_t st_blocks; /* Number of 512B blocks allocated */该文件占用多少块512字节为一块/* Since Linux 2.6, the kernel supports nanosecondprecision for the following timestamp fields.For the details before Linux 2.6, see NOTES. */struct timespec st_atim; /* Time of last access */// 最后访问时间struct timespec st_mtim; /* Time of last modification */// 最后修改时间(修改了用户数据---即文件内容)struct timespec st_ctim; /* Time of last status change */// 最后修改时间 (修改了属性信息,inode中的内容)#define st_atime st_atim.tv_sec /* Backward compatibility */#define st_mtime st_mtim.tv_sec#define st_ctime st_ctim.tv_sec
}; 解析权限和类型的方式: mode_t st_mode; /* File type and mode */// 保存了文件的权限和类型st_mode使用位域实现,可以使用以下的宏去解析这个变量如:// 获取文件的属性struct stat st; int r stat(1.txt, st);st.st_mode就保存了1.txt的文件的权限和类型文件的类型S_IFMT 0170000 bit mask for the file type bit fieldS_IFSOCK 0140000 socket 套接字文件S_IFLNK 0120000 symbolic link 链接文件S_IFREG 0100000 regular file 普通文件S_IFBLK 0060000 block device 块设备文件S_IFDIR 0040000 directory 目录文件S_IFCHR 0020000 character device 字符设备文件S_IFIFO 0010000 FIFO 管道文件to test for a regular file (for example)if ((st.st_mode S_IFMT) S_IFREG) {// 当前文件是一个普通文件}或者:printf(File type: );switch (sb.st_mode S_IFMT) {case S_IFBLK: printf(block device\n); break;case S_IFCHR: printf(character device\n); break;case S_IFDIR: printf(directory\n); break;case S_IFIFO: printf(FIFO/pipe\n); break;case S_IFLNK: printf(symlink\n); break;case S_IFREG: printf(regular file\n); break;case S_IFSOCK: printf(socket\n); break;default: printf(unknown?\n); break;}或者:S_ISREG(m) is it a regular file? 普通文件S_ISDIR(m) directory? 目录S_ISCHR(m) character device? 字符设备文件S_ISBLK(m) block device? 块设备文件S_ISFIFO(m) FIFO (named pipe)? 管道文件S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.) 链接文件S_ISSOCK(m) socket? (Not in POSIX.1-1996.) 套接字文件stat(pathname, st);if (S_ISREG(st.st_mode)) {// 当前文件是一个普通文件}
-----------------------------------------------------------------------------文件的权限信息printf(Mode: %lo (octal)\n, (unsigned long)st.st_mode); if (st.st_mode S_IRUSR) {// 用户拥有可读的权限} else {// 用户,不拥有可读的权限}S_IRWXU 00700 user (file owner) has read, write, and execute permission用户拥有可读可写可执行的权限S_IRUSR 00400 user has read permission、S_IWUSR 00200 user has write permissionS_IXUSR 00100 user has execute permissionS_IRWXG 00070 group has read, write, and execute permissionS_IRGRP 00040 group has read permissionS_IWGRP 00020 group has write permissionS_IXGRP 00010 group has execute permissionS_IRWXO 00007 others have read, write, and execute permissionS_IROTH 00004 others have read permissionS_IWOTH 00002 others have write permissionS_IXOTH 00001 others have execute permissionU/USR 用户(文件的拥有者)G/GRP 组用户O/OTH 其他用户不管你使用的是哪一个函数(stat/fstat/lstat)都是获取到上面结构体的信息注意时间格式转化struct timespec {time_t tv_sec; /* seconds */// 秒,记录的是从1970年1月1日到现在的秒数long tv_nsec; /* nanoseconds */ // 纳秒
};1s 1000 ms
1ms 1000 us
1us 1000 ns 既然你给出的是一个秒数,如何把一个秒数转化为时间呢?
#include time.hchar* ctime(const time_t *timep); // 把秒数转化为时间字符串timep你要转化的秒数可以把当前的秒数转化为一个表示时间的字符串如:printf(%s\n, ctime(st.st_atim.tv_sec));or printf(%s\n, ctime(st.st_atime));
----------------------------------------------------------------------------
struct tm* localtime(const time_t *timep);
可以把一个当前的秒数转化为一个表示时间的结构体struct tm {int tm_sec; /* Seconds (0-60) */int tm_min; /* Minutes (0-59) */int tm_hour; /* Hours (0-23) */int tm_mday; /* Day of the month (1-31) */int tm_mon; /* Month (0-11) */ // 须1int tm_year; /* Year - 1900 */int tm_wday; /* Day of the week (0-6, Sunday 0) */int tm_yday; /* Day in the year (0-365, 1 Jan 0) */int tm_isdst; /* Daylight saving time */
}; 如何获取系统时间:NAMEtime - get time in seconds
SYNOPSIS#include time.htime_t time(time_t *tloc); time_t tm time(NULL);
or
time_t tm;
time(tm);
--------------------------------------------------------------------------
如何获取更加精准的时间:
NAMEgettimeofday, settimeofday - get / set time
SYNOPSIS#include sys/time.h// 微秒级别的时间
int gettimeofday(struct timeval *tv, struct timezone *tz);struct timeval {time_t tv_sec; /* seconds */suseconds_t tv_usec; /* microseconds */
};// 纳秒级别的时间
int clock_gettime(clockid_t clk_id, struct timespec *tp);struct timespec {time_t tv_sec; /* seconds */// 秒,记录的是从1970年1月1日到现在的秒数long tv_nsec; /* nanoseconds */ // 纳秒
}; 10. 目录操作 目录在Linux中也是文件我们能不能按照操作普通文件的方式去操作目录呢? 普通文件 打开文件 读写文件 关闭文件 如果可以那么目录的内容是什么呢? 在Linux中目录也是文件也可以使用open打开(O_RDONLY)不能写打开只能以读的方式打开 也会返回一个文件描述符但是我们使用read去读取内容的时候会失败 read failed: Is a directory 在linux下面不能使用read去读一个目录 那么目录文件应该如何操作呢? 1目录和普通文件的区别 在Linux中任何一个文件只要存在就有自己的inode编号 目录文件也有自己的inode同样保存了文件的属性信息 (stat同样可以获取属性) 但是目录文件的内容和普通文件的内容有很大的差别 普通文件的内容就是用户记录的一些用户数据 目录文件的内容记录的文件和文件之间的组织关系叫做目录项 可以理解为一个二维表格记录着当前目录下面的文件名和inode的对应关系 在创建一个空目录的时候系统会自动的为目录预留一个目录项数组 把该目录下面的所有文件(第一层)都记录在这个数组中 2目录操作的API函数 a.打开目录(opendir)NAMEopendir, fdopendir - open a directory
SYNOPSIS#include sys/types.h#include dirent.hDIR* opendir(const char *name);DIR *dir opendir(/home/china);
------------------------------------------------------------------
DIR* fdopendir(int fd);int fd open(/home/china, O_RDONLY);
DIR *dir fdopendir(fd);name/fd你要打开的目录的路径名/文件描述符返回值:成功就会返回一个DIR指针(指向当前目录项的指针)失败返回NULL,同时errno被设置在Linux中,使用DIR结构体表示一个打开的目录,至于目录里面有什么,
不需要关心,只需要知道DIR类型的指针表示一个已经打开的目录就可以了,
后序操作这个目录的时候,使用这个指针表示这个目录即可 b.读取目录项(readdir)通过读取目录项,就可以知道目录中有哪些文件了
NAMEreaddir - read a directory
SYNOPSIS#include dirent.hreaddir是用来从dirp指向的目录中,读取下一个目录项的指针,
一个目录中有多少个目录项,就有多少个文件每一次调用readdir,都会给你返回一个指向目录项的指针,
并且让指针指向下一个目录项(偏移量),直到返回NULL,表示读取完毕struct dirent* readdir(DIR *dirp);dirp指向你要读取目录项的目录(是opendir的返回值)In the glibc implementation, the dirent structure isdefined as follows:
struct dirent {
---ino_t d_ino; /* Inode number */// 当前读取到的目录项的inode编号off_t d_off; /* Not an offset; see below */// 目录项的偏移unsigned short d_reclen; /* Length of this record */// 该结构体的长度unsigned char d_type; /* Type of file; not supported by all filesystem types */// 读取到的目录项指向的文件的类型不是所有的文件系统都支持
---char d_name[256]; /* Null-terminated filename */// 该目录项指向的文件名字读取到的是相对路径---目录下面的文件名
};注意:该结构体的成员,只有d_ino,和d_name两个成员是所有文件系统都支持的,
如果你想要你的代码有更好的兼容性和可移植性,在代码中尽量只使用这两个成员返回值:On success, readdir() returns a pointer to a dirent structure.
(This structure may be statically allocated; do not attempt to free(3) it.)如果成功readdir()返回一个指向struct dirent *的结构体指针
(此结构体是静态分配的;不要试图去释放它)If the end of the directory stream is reached, NULL is returned
and errno is not changed. If an error occurs, NULL is returned and errno is set appropriately. 成功返回一个指向目录项struct dirent *的指针读完后会返回NULL,errno不会
被设置失败返回NULL并且errno被设置 c.关闭目录(closedir)NAMEclosedir - close a directory
SYNOPSIS#include sys/types.h#include dirent.hint closedir(DIR *dirp);dirp指向你要关闭的目录的DIR结构体返回值成功返回0失败返回-1,同时errno被设置 创建目录NAMEmkdir, mkdirat - create a directory
SYNOPSIS#include sys/stat.h#include sys/types.hint mkdir(const char *pathname, mode_t mode);相对路径以工作路径作为参照点返回值成功返回0失败返回-1同时errno被设置如果目录已存在nkdir会失败返回-1同时errno EEXIST 打印指定目录下所有文件的inode和名字 #include stdio.h
#include sys/types.h
#include dirent.hint main(int argc, char *argv[]) {// 打开目录DIR *dirp opendir(argv[1]);if (dirp NULL) {perror(opendir failed);return -1;}// 打印指定目录下所有文件的inode和名字struct dirent *dp NULL;while (dp readdir(dirp)) {printf(inode %ld,name %s\n, dp-d_ino, dp-d_name);}// 关闭目录closedir(dirp);return 0;
} 三. 练习 1. 写一个简单的程序判断一个目录中有多少个子目录 (第一级) #include stdio.h
#include sys/types.h
#include sys/stat.h
#include dirent.h
#include unistd.hint main(int argc, char *argv[]) {// 打开目录DIR *dirp opendir(argv[1]);if (dirp NULL) {perror(opendir failed);return -1;}// 保存当前的工作路径char cur_path[256] {0};getcwd(cur_path, 256);// 进入目标目录(argv[1]表示的目录)chdir(argv[1]);// 获取目标目录的绝对路径char abs_path[256] {0};getcwd(abs_path,256);// 回到当前的工作路径chdir(cur_path);// 打印指定目录下所有文件的inode和名字int num 0;struct dirent *dp NULL;while (dp readdir(dirp)) {struct stat st;// 读取到的仅仅是名字,把单纯的名字变成绝对路径名char name[512] {0};sprintf(name, %s/%s, abs_path, dp-d_name);// printf(%s\n, name);stat(name, st);if (S_ISDIR(st.st_mode)) {num;}}printf(%d\n, num);// 关闭目录closedir(dirp);return 0;
}2. 利用上面的API函数(stat)写一个程序能够实现类似于ls -l 的功能 ./myls 1.txt 能够把1.txt的所有详细信息展示出来 #include stdio.h
#include sys/types.h
#include sys/stat.h
#include unistd.h
#include pwd.h
#include time.hint main(int argc, char *argv[]) {if (argc ! 2) {printf(USER:./myls filename);return -1;}// 获取文件的属性(stat)struct stat st;int ret stat(argv[1], st);if (ret -1) {perror(stat failed);return -1;}// 解析文件的属性char buf[512] {0}; // 定义一个字符数组,保存结果int r 0;//文件的类型if (S_ISREG(st.st_mode)) {// 当前文件是一个普通文件// printf(-); // 按照格式输出数据到标准输出(终端)// 用法类似于printf,sprintf是把内容输出到指定的内存地址// 返回实际输出的字节数量r sprintf(buf, -);} else if (S_ISDIR(st.st_mode)) {r sprintf(buf, d);} else if (S_ISCHR(st.st_mode)) {r sprintf(buf, c);} else if (S_ISBLK(st.st_mode)) {r sprintf(buf, b);} else if (S_ISFIFO(st.st_mode)) {r sprintf(buf, p);} else if (S_ISLNK(st.st_mode)) {r sprintf(buf, l);} else if (S_ISSOCK(st.st_mode)) {r sprintf(buf, s);}// 文件的权限if (st.st_mode S_IRUSR) {// 用户拥有可读的权限r sprintf(buf r, r);} else {// 用户,不拥有可读的权限r sprintf(buf r, -);}(st.st_mode S_IWUSR) ? (r sprintf(buf r, w)) : (r sprintf(buf r, -));(st.st_mode S_IXUSR) ? (r sprintf(buf r, x)) : (r sprintf(buf r, -));(st.st_mode S_IRGRP) ? (r sprintf(buf r, r)) : (r sprintf(buf r, -));(st.st_mode S_IWGRP) ? (r sprintf(buf r, w)) : (r sprintf(buf r, -));(st.st_mode S_IXGRP) ? (r sprintf(buf r, x)) : (r sprintf(buf r, -));(st.st_mode S_IROTH) ? (r sprintf(buf r, r)) : (r sprintf(buf r, -));(st.st_mode S_IWOTH) ? (r sprintf(buf r, w)) : (r sprintf(buf r, -));(st.st_mode S_IXOTH) ? (r sprintf(buf r, x)) : (r sprintf(buf r, -));// 硬链接数量r sprintf(buf r, %ld, st.st_nlink);// 文件属主,文件组struct passwd *pw getpwuid(st.st_uid);r sprintf(buf r, %s, pw-pw_name);pw getpwuid(st.st_gid);r sprintf(buf r, %s, pw-pw_name);// 文件大小r sprintf(buf r, %8ld, st.st_size);// 修改时间(mtime)struct tm *pt localtime(st.st_atim.tv_sec);r sprintf(buf r, %3d月, pt-tm_mon 1);r sprintf(buf r, %4d日, pt-tm_mday);r sprintf(buf r, %d:%d , pt-tm_hour, pt-tm_min);// 文件名r sprintf(buf r, %s, argv[1]);printf(%s\n, buf);return 0;
} 3. 写一个代码可以计算一个文件夹的大小 大小定义为该目录下面所有文件以及目录下面的目录里面的文件.....的大小之和 #include stdio.h
#include sys/types.h
#include sys/stat.h
#include unistd.h
#include time.h
#include dirent.h
#include string.h// 获取目录中文件的总大小
int getDirSize(const char *pathname) {int size 0;// 打开目录DIR *dirp opendir(pathname);if (dirp NULL) {perror(opendir failed);return -1;}// 读取目录项struct dirent *dp NULL;while (dp readdir(dirp)) { // 读取到的仅仅是名字,把单纯的名字变成绝对路径名char name[512] {0};sprintf(name, %s/%s, pathname, dp-d_name);// 排除当前目录下面的.和..if (!strcmp(dp-d_name, .) || !strcmp(dp-d_name, ..)) {continue;} // printf(name:%s\n, dp-d_name);// 判断读取到的是否为目录文件struct stat st;int ret stat(name, st);if (ret -1) {perror(stat failed);return -1;}if (S_ISDIR(st.st_mode)) {// 当前文件是目录文件,需要递归的查找子目录size getDirSize(name);} else if (S_ISREG(st.st_mode)) {// 当前读取的是普通文件,计算大小size st.st_size;}}//关闭目录closedir(dirp);return size;}int main(int argc, char *argv[]) {if (argc ! 2) {printf(USER:./a.out directory);return -1;}// 先判断是文件还是目录struct stat st;int ret stat(argv[1], st);if (ret -1) {perror(stat failed);return -1;}if (!S_ISDIR(st.st_mode)) {printf(not a directory!);return -1;}// 把argv[1]表示的目录变成一个绝对路径// 保存当前的工作路径char cur_path[256] {0};getcwd(cur_path, 256);// 进入目标目录(argv[1]表示的目录)chdir(argv[1]);// 获取目标目录的绝对路径char abs_path[256] {0};getcwd(abs_path, 256);// 回到当前的工作路径chdir(cur_path);// 获取目录中文件的总大小int size getDirSize(abs_path);printf(size %d\n, size);return 0;
} 4. 写一个程序能够搜索指定文件夹下面所有以.mp3或者.bmp结尾的文件 并且把搜索到的所有文件的绝对路径保存到一个带管理者结点的双向链表中 返回链表的管理者结点的地址 ./a.out /home/china BothWithLinkedListWithHead.h #ifndef __BOTHWITHLINKEDLISTWITHHEAD_H__
#define __BOTHWITHLINKEDLISTWITHHEAD_H__#include stdio.h
#include stdlib.h
#include string.h// 数据结点
struct node {char name[512]; // 数据域 存储数据struct node *next; // 指针域 保存逻辑上的下一个(关系)struct node *prev; // 指针域 保存逻辑上的上一个(关系)
};// 头结点的数据类型 保存链表的属性
struct list {struct node *first; // 指向第一个数据结点struct node *last; // 指向最后一个数据结点int NodeNum; // 记录链表的长度/*其他属性*/
};struct list* create_list();
void print_list(struct list *list);
struct list* destroy_list(struct list *list);
void insert_node(struct list *list, char *name);#endif BothWithLinkedListWithHead.c #include BothWithLinkedListWithHead.h// 创建一个链表
struct list* create_list() {struct list *list malloc(sizeof(*list));list-first NULL;list-last NULL;list-NodeNum 0;return list;
}// 遍历
void print_list(struct list *list) {// 异常处理if (list NULL || list-NodeNum 0) {return ;}struct node *p list-first; // 遍历指针while (p) {printf(%s\n, p-name);p p-next;}
}/*destroy_list销毁带头双链表
*/
struct list* destroy_list(struct list *list) {// 异常处理if (list NULL) {return NULL;}struct node *pc list-first; // 指向被删结点while (pc) // 头删{list-first list-first-next;if (list-first) {list-first-prev NULL;}pc-next NULL;free(pc);pc list-first;}// 删除头结点list-last NULL;free(list);list NULL;return list;
}// 尾插
void insert_node(struct list *list, char *name) {if (list NULL) {return ;}// 创建一个新节点struct node *pnew (struct node*)malloc(sizeof(*pnew));strcpy(pnew-name, name);pnew-next NULL;pnew-prev NULL;// 插入if (list-first NULL) { // 从无到有list-first list-last pnew;} else { // 尾插list-last-next pnew;pnew-prev list-last;list-last pnew;}list-NodeNum;
}dir.h #ifndef __DIR_H__
#define __DIR_H__#include string.h
#include sys/types.h
#include dirent.h
#include sys/stat.h
#include unistd.h
#include BothWithLinkedListWithHead.hint isBMP(char *filename);
int isJPG(char *filename);
void find_dir_pic(const char *dirname, struct list *list);#endif dir.c #include dir.h// 判断一个文件名是否为bmp图片
int isBMP(char *filename) {int len strlen(filename);char s[8] {0};// 把最后的4个字符复制出来strcpy(s, (filename len - 4));int x strcmp(.bmp, s);if (x 0) {return 1;} else {return 0;}
}// 判断一个文件名是否为jpg图片
int isBMP(char *filename) {int len strlen(filename);char s[8] {0};// 把最后的4个字符复制出来strcpy(s, (filename len - 4));int x strcmp(.jpg, s);if (x 0) {return 1;} else {return 0;}
}/*find_dir_pic:把目录下面的所有图片文件的绝对路径名加入到指定的链表dirname:你要查找的目录名称list:你要把找到的图片名字加入到的链表头节点指针
*/
void find_dir_pic(const char *dirname, struct list *list) {// 打开目录DIR *dirp opendir(dirname);if (NULL dirp) {perror(opendir failed);return ;}// 读取目录项struct dirent *dp NULL;while (dp readdir(dirp)) {// 排除当前目录下的./..if (!strcmp(dp-d_name, .) || !strcmp(dp-d_name, ..)) {continue;}// printf(name:%s\n,dp-d_name); // 获取的是单纯的名字// 把单纯的名字变成绝对路径名char name[512] {0};sprintf(name, %s/%s, dirname, dp-d_name);// printf(%s\n, name); // 判断是否为目录struct stat st;int ret stat(name, st);if (ret -1) {perror(stat failed);} if (S_ISDIR(st.st_mode)) { // 递归的查找子目录find_dir_pic(name, list);} else if (S_ISREG(st.st_mode)) {// 判断是否为图片文件if (isBMP(name)) {insert_node(list, dp-d_name);}if (isJPG(name)) {insert_node(list, dp-d_name);}}}// 关闭目录closedir(dirp);return;
} main.c #include BothWithLinkedListWithHead.h
#include dir.hint main(int argc, char *argv[]) {if (argc ! 2) {printf(USER:./a.out directory);return -1;}// 先判断是文件还是目录struct stat st;int ret stat(argv[1], st);if (ret -1) {perror(stat failed);return -1;}if (!S_ISDIR(st.st_mode)) {printf(not a directory!);return -1;}// 创建一个链表struct list *list create_list();// 把argv[1]表示的目录变成一个绝对路径// 保存当前的工作路径char cur_path[256] {0};getcwd(cur_path,256);// 进入目标目录(argv[1]表示的目录)chdir(argv[1]);// 获取目标目录的绝对路径char abs_path[256] {0};getcwd(abs_path,256);// 回到当前的工作路径chdir(cur_path);// 获取目录中所有的图片名称find_dir_pic(abs_path, list);// 打印链表print_list(list);// 销毁链表destroy_list(list);return 0;
} 5. 复制目录把 src目录里面的所有文件(包括子目录)全部复制到 dest目录中 Sasuke 20:05:02
/*功能复制目录把 src目录里面的所有文件(包括子目录)全部复制到 dest目录中失败返回-1成功返回0
*/
int copy_dir(const char *src,const char *dest)
{int r mkdir(dest,0777);//创建目的目录如果目录已经存在 mkdir会失败//但是这种情况并不影响我们后续的操作所有不需要结束函数//如果是其他原因导致的创建失败那就需要结束函数if(-1 r errno ! EEXIST){printf(创建%s目录失败:%s\n,dest,strerror(errno));return -1;}DIR * psrc opendir(src);if(NULL psrc){printf(打开%s文件失败:%s\n,src,strerror(errno));return -1;}DIR * pdest opendir(dest);if(NULL pdest){printf(打开%s文件失败:%s\n,dest,strerror(errno));closedir(psrc);return -1;}struct dirent * p;//char srcpathname[100]; //可能会越界最好是动态分配//char destpathname[100]; int srclen;int destlen;char * srcpathname;char * destpathname;struct stat st;//用来保存获取到的文件属性信息while(1)//一项一项读取目录{p readdir(psrc);if(NULL p)//返回NULL代表目录读取完毕break;//printf(%s\n,p-d_name);//可以发现包含两个特殊目录: . ..if(strcmp(p-d_name,.)0 || strcmp(p-d_name,..)0)continue;//连接原目录及该目录中的文件组成一个完整的路径名srclen strlen(src) 1 strlen(p-d_name) 1;srcpathname (char *)malloc(srclen);strcpy(srcpathname,src);strcat(srcpathname,/);strcat(srcpathname,p-d_name);printf(%s\n,srcpathname);r stat(srcpathname,st);if(-1 r){perror();continue;}destlen strlen(dest) 1 strlen(p-d_name) 1;destpathname (char *)malloc(destlen);strcpy(destpathname,dest);strcat(destpathname,/);strcat(destpathname,p-d_name);printf(%s\n,destpathname);if((st.st_mode S_IFMT) S_IFREG)//srcpathname是普通文件进行复制{r copy_file(srcpathname,destpathname);if(-1 r)//该文件复制失败{printf(%s文件复制失败\n,srcpathname);}}else if((st.st_mode S_IFMT) S_IFDIR)//是子目录{//递归调用自己copy_dir(srcpathname,destpathname);}free(srcpathname);free(destpathname);}//原目录 /mnt/hgfs/mnt/hgfs/CS2415F/2阶段// /mnt/hgfs/mnt/hgfs/CS2415F/2阶段/1文件IO
//目录目录 /home/china/test// /home/china/test/1文件IOclosedir(psrc);closedir(pdest);return 0;
}int main(int argc,char *argv[])
{if(argc ! 3){printf(输入有误需要带两个参数原目录路径和目的目录路径\n);return -1;}int r copy_dir(argv[1],argv[2]);if(-1 r){printf(复制失败\n);}else{printf(复制成功\n);}return 0;
} 4. dup2 #include unistd.hint dup2(int oldfd, int newfd);功能描述:dup2函数将oldfd所指定的文件描述符复制到newfd上使得newfd也指向oldfd所引用的文件或套接字、管道等如果newfd已经打开则dup2会先关闭它然后再进行复制操作如果oldfd和newfd相等则dup2会直接返回newfd而不会关闭它返回值成功时dup2返回newfd失败时返回-1并设置相应的errno值以指示错误类型 #include stdio.h
#include unistd.h
#include fcntl.h int main() { int fd open(output.txt, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); if (fd -1) { perror(Failed to open file); return 1; } // 将文件描述符fd复制到标准输出STDOUT_FILENO if (dup2(fd, STDOUT_FILENO) -1) { perror(Failed to duplicate file descriptor); close(fd); // 不要忘记关闭原始的文件描述符 return 1; } // 此时STDOUT_FILENO已经指向output.txt下面的printf输出会写入该文件 printf(Hello, dup2!\n); // 关闭原始的文件描述符因为STDOUT_FILENO已经指向了相同的文件 close(fd); return 0;
} 注意事项 文件描述符的有效性传递给dup2的两个文件描述符必须是有效的关闭原始文件描述符虽然dup2不会关闭oldfd但在很多情况下复制完成后关闭oldfd是一个好习惯特别是当你不再需要它时错误处理检查dup2的返回值并在失败时适当处理错误原子操作dup2是一个原子操作即它先关闭newfd(如果已打开)然后将oldfd复制到newfd这两个步骤是作为一个整体来执行的不会被中断