单片机是“裸奔”的,
APP 你 要吃猪肉
-------- 屠夫
Hardware 杀猪
特点:
简单,直接操作硬件。
缺点:
搞应用的必须要了解硬件的实现细节;
无并发,不能同时允许多个程序,它是“单任务”
“带OS”
APP 你要吃猪肉
------------
os 钱 屠夫
------------
Hardware 杀猪
特点:
开发的可以把业务放在你的应用开发业务逻辑,
不必关心硬件的具体实现细节;
提供了并发功能,允许同时跑多个应用,“多任务”
os? Operating system(操作系统)是软件
功能:
操作系统是管理和分配硬件资源的系统软件
linux操作系统进行应用开发,就是调用操作系统linux的API函数
接口去操作硬件,或者说去使用linux提供的服务
如:
open
close
read
write
....
1.linux是一个开源的操作系统
Everything is a file in linux(Unix).
在linux下面,任何东西都是文件,
或者说,在linux下面,操作任何东西,都是
操作文件,都是通过文件的接口去实现
文件系统:用来存储、组织、管理文件的一套方式、方法、协议
及其软件实现等等
文件:
文件属性:i-node. 唯一标识一个文件存在与否
文件内容
linux内核中
struct inode{} 用来描述一个文件的物理inode的信息
系统识别到一个文件的存在了,就会为它创建
一个struct inode,一个文件只会唯一对应一个
struct inode
struct file{} 用来描述已经打开的文件
文件状态标记(如:O_RDONLY,O_WRONLY,...)
文件偏移量/offset(类似于,”光标“)
struct inode *
每一个打开的文件都会对应一个struct file
一个文件可以被不同的程序(进程)打开
操作文件过程
struct file --> struct inode -->硬件上的inode ---》文件内容
linux为了屏蔽文件操作的具体实现细节,它会为每个进程
创建一个”进程的文件表项“:struct file *的数组
保存每个进程打开的文件的struct file的地址
struct file* fds[]
0 struct file* ---> struct file --->struct inode
1
2
3
4
...
linux再提供了操作文件的函数接口:
open 返回值,返回”进程文件表项“的下标, int
"文件描述符":在linux应用中,用来描述一个打开的文件,
一个打开的文件的id是唯一,后续操作该文件,都是通过该文件
描述符。
read
write
close
-------------
linux系统提供的这些用于文件的接口函数(如:open, read,write,lseek...)
我们称之为”系统IO“
2.linux”系统IO“使用
在linux系统文件操作的接口函数:
(1)打开文件open函数
头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数的功能
open在linux系统下,用来打开或者创建(创建并且打开)一个文件的
函数的原型
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数的参数
const char *pathname:要打开或者创建的文件名,带路径(如果不带路径,就是当前路径)
int flags:打开文件的标记
O_RDONLY:read only 只读
O_WRONLY:write only 只写
O_RDWR: read and write 读写
以上三个标记只能选一个(文件打开的方式)
O_APPEND:追加标记,打开文件后,文件偏移量会在文件末尾;
O_CREAT:创建标记,如果文件不存在,则创建它
O_EXCL:该标志一般和O_CREAT配合使用,用来测试文件是否存在的。
如果你指定 O_CREAT | O_EXCL,如果文件不存在,则open会
失败;并且errno == EEXIST
O_TRUNC:truncate 截短
文件截短,把文件内容清0
O_NONBLOCK: block 非阻塞方式打开文件
非阻塞 :”不等待“
如果文件没有内容,read不会阻塞,直接返回一个错误
如果文件没有空间啦,write不会阻塞,直接返回一个错误
阻塞:”等待“(默认情况)
如果文件没有内容,read会阻塞(直到有数据或者出错)
如果文件没有空间啦,write会阻塞(直到可写或出错)
。。。
多个标记用”|“连接
如:
O_RDWR | O_CREAT | O_TRUNC
mode_t mode:指定创建的文件的权限,当第二参数中带”O_CREAT“
有两种文件指定:rwx rwx rwx
(1)S_IRUSR S_IWUSR S_IXUSR "用户"
S_IRGRP S_IWGRP S_IXGRP "组用户"
S_IROTH S_IWOTH S_IXOTH "其它用户"
如:S_IRUSR | S_IRGRP | S_IROTH
=》r-- r-- r--
(2) 0666
=>
110 110 110
函数的返回值:
成功:返回一个打开的文件描述符(>2)
因为操作系统会自动为每个进程打开三个文件
标准输入文件 文件描述符为 STDIN_FILENO(0)
标准输出文件 文件描述符为 STDOUT_FILENO(1)
标准出错文件 文件描述符为 STDERR_FILENO(2)
失败:返回-1,同时errno被设置
man errno
errno 是一个全局变量,#include <errno.h>
是用来保存最后一个出错的错误码
perror 把错误吗对应的提示打印出来
perror("user indi");
=>user indi:错误码的提示信息
打印出来的:
用户自己的提示信息:系统的错误码的提示信息
(2)操作文件(read write close ...)
read函数
头文件
#include <unistd.h>
函数功能
将从fd所代表的文件中读取count个字节数到buf所指向的区域
函数原型
ssize_t read(int fd, void *buf, size_t count);
函数参数
int fd //文件描述符,表示你要从哪个文件中读
void *buf //指向一段内存的首地址,表示你要把文件内容
读到哪里去
size_t count //表示你要读取的字节数
函数返回值
>0 :返回实际读取到的字节数(有可能 <= count)
=-1:失败,同时errno被设置。
=0:表示已经读到了文件末尾啦。
NOTE:
文件的偏移量("光标位置")会由内核自动维护,
一般来说,打开文件时,offset = 0,每次成功
read /write 了count个字节后,offset +=count
----------------------------------------------------------
write函数
头文件
#include <unistd.h>
函数功能
将buf所指向的区域里面的数据的count个字节数数据写入到fd所代表的文件
函数原型
ssize_t write(int fd, const void *buf, size_t count);
函数参数
int fd //文件描述符 表示你要写到哪个文件中去
const void *buf //指向要写的内容所在的首地址
size_t count //要写多少个字节
函数返回值
>0:表示实际写成功的字节数
=0:表示什么也没写
=-1:表示出错了,同时errno被设置。
lseek函数
头文件
#include <sys/types.h>
#include <unistd.h>
函数功能
用来重定位文件的偏移量{"光标位置"}的
函数原型
off_t lseek(int fd, off_t offset, int whence);
函数参数
int fd //文件描述符
off_t offset //偏移量,具体的新偏移量的位置 需要集合第三个参数:
int whence //定位方式,有如下三种:
SEEK_SET:基于文件开头定位
新光标位置 = 文件开头 + offset (>=0)
SEEK_CUR:基于当前光标定位
新光标位置 = 当前光标位置 + offset (可正可负)
SEEK_END:基于文件末尾定位
新光标位置 = 文件末尾 + offset(可正可负)
函数返回值:
成功:返回新光标位置离文件开头的字节数
失败:返回-1,同时errno被设置。
(3)关闭文件 close函数
头文件
#include <unistd.h>
函数功能
用来关闭fd指定的文件
函数原型
int close(int fd);
函数参数
int fd //文件描述符
函数返回值
成功: 返回0
失败: 失败返回 -1,同时errno被设置。
3.umask函数
头文件
#include <sys/types.h>
#include <sys/stat.h>
函数功能
设置一个文件在创建时的掩码。
设置的权限位为1的bit,你在创建这个文件时不能指定
linux默认的umask 是 022 -》 000 010 010
默认情况下,创建文件时,不能指定S_IWGRP 和 S_IWOTH
函数原型
mode_t umask(mode_t mask);
函数的参数
mode_t mask //指定文件的掩码
函数返回值
返回系统使用的上一个的文件掩码
4.每个进程都会有字节的工作目录,
获取进程的当前的工作目录(getwd)
头文件
#include <unistd.h>
函数功能
用来获取进程当前工作目录
函数原型
char *getwd(char *buf);
函数参数
char *buf //用来保存进程当前工作目录的”绝对路径“,”字符串“
函数返回值
成功:返回当前工作目录字符串的首地址
失败: 返回NULL,同时errno被设置。
注意:
getwd 有一个巨大的bug? 你懂的。有越界的风险。
如果buf指向的空间,不够大(当前目录字符串,超过buf所指向空间长度),
getwd就有可能访问buf后面的内存空间,这样就有可能会造成内存的非法访问
为了解决getwd的这个bug,getcwd出现啦
头文件
#include <unistd.h>
函数功能
用来获取当前进程的工作目录
函数原型
char *getcwd(char *buf, size_t size);
函数参数
char *buf //指向的空间用来保存当前的工作目录的路径字符串的
size_t size //指定buf指向的空间的最大长度
函数返回值
成功: 返回当前工作目录的字符串的首地址
失败: 返回NULL,同时errno被设置。
失败的原因可能是:
如果当前工作目录的路径字符串长度 > size -1,这个函数就会报错
get_current_dir_name 也是用来获取进程当前工作目录的绝对路径 需要在头文件前面
添加 #define _GNU_SOURCE 这个宏定义
头文件
#include <unistd.h>
函数功能
用来获取进程当前工作目录的绝对路径名的
只不过,这个函数在内部自动malloc足够长的空间
来保存当前工作目录的路径名,并返回这个首地址。
所以为了防止内存泄漏,调用这在使用完后,要free这个空间!!!
函数原型
char *get_current_dir_name(void);
函数返回值
成功: 返回当前工作目录的字符串的首地址
失败: 返回NULL,同时errno被设置。
5.改变进程的当前工作目录
头文件
#include <unistd.h>
函数功能
改变进程的当前工作目录
函数原型
int chdir(const char *path);
int fchdir(int fd);
函数参数
const char *path //填要切换到的工作目录的,目录字符串
如:"/home/china/gh"
int fd //填要切换的工作目录的,文件描述符
如: int fd = open("/home/china",O_RDONLY);
打开目录只能以读的标记打开
函数返回值
成功: 返回0
失败: 返回-1,同时errno被设置。
6.文件截短(truncate/ftruncate函数)
头文件
#include <unistd.h>
#include <sys/types.h>
函数功能
用来给一个指定的文件截短到指定的长度
函数原型
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
函数参数
const char *path //填要截短的文件名
off_t length //截短后的文件内容的长度
length < 原文件的长度 ”截短“
length > 原文件的长度 ”扩展“,”留空洞“
函数返回值
成功:返回0
失败:返回-1,同时errno被设置。
7.删除一个文件
unlink函数
头文件
#include <unistd.h>
函数功能
用来删除一个普通文件或者结点的
函数原型
int unlink(const char *pathname);
函数参数
const char *pathname //要删除的哪个文件的文件名(带路径)
函数返回值
成功: 返回0
失败:返回-1,同时errno被设置。
rmdir函数
头文件
#include <unistd.h>
函数功能
删除一个空目录
函数原型
int rmdir(const char *pathname);
函数参数
const char *pathname //要删除的那个文件的文件名
函数返回值
成功:返回0
失败:返回-1,同时errno被设置
remove函数
头文件
#include <stdio.h>
函数功能
用来删除一个普通文件或空目录
函数原型
int remove(const char *pathname);
函数参数
const char *pathname //要删除的文件的名或要删除的空目录名
函数返回值
成功:返回0
失败:返回-1,同时errno被设置。
作业:
1.利用linux提供的系统IO函数(如:open/close/read/write/lseek)
实现两个文件的内容拷贝
cp 1.txt 2.txt
思路
fd1 = open(1,);
fd2 = open(2,)
while(fd1没有读完)
{
read(fd1..)
write(fd2..)
}
close(fd1)
close(fd2)
今日总结:
1.要理解”裸奔层次“与"linux系统层次"区别
计算机科学中的任何问题,都可以通过加上一层逻辑来解决
2.linux文件操作的大致流程
struct inode
struct file
以及这两者之间的关系?
文件描述符是什么东西?
文件描述符是linux系统提供给应用的接口,为了唯一标识一个
已经打开的文件的,是一个>=0 的整数
在linux中,文件描述符是用来描述或标识一个已经打开的
文件。
系统IO是什么?
linux系统提供给应用用来input/output(输入/输出)操作的
函数接口,如:open/read/write/lseek/close...
3.至于open/read/write。。。。等等这些API函数的具体的用法,以及参数
man
1.获取文件属性
stat, fstat, lstat函数
头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
函数功能
用来获取path or fd 指定的那个文件的属性信息
函数原型
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
函数参数
const char *pathname //要获取属性的文件名
struct stat *statbuf //指向的结构体用来保存文件的属性信息
int fd //要获取属性的文件描述符,先open它
函数返回值
成功:返回0
失败:返回-1,同时errno被设置
注意:
lstat功能与stat相同,只不过,当path是一个符号链接时,
lstat获取的是符号链接文件本身的属性信息,而stat是获取
的符号链接文件指向的那个文件的属性信息的。
在linux下面,有一个结构体是用来保存一个文件的状态或属性信息的,
struct stat:
struct stat {
dev_t st_dev; /* 用来表示容纳该文件的那个设备的设备号 */
ino_t st_ino; /* 该文件的inode的编号 */
mode_t st_mode; /* 文件权限位(包含文件的类型) */
解析 st_mode包含文件的类型与权限,用位域(bit),可以用下列宏来解析:
struct stat st
st.st_mode
S_ISREG(st.st_mode) 为真则表示该文件是 普通文件 -
S_ISDIR(st.st_mode) 为真则表示该文件是 目录 d
S_ISCHR(st.st_mode) 为真则表示该文件是 字符设备 c
S_ISBLK(st.st_mode) 为真则表示该文件是 块设备 b
S_ISFIFO(st.st_mode)为真则表示该文件是 管道文件 p
S_ISLNK(st.st_mode) 为真则表示该文件是 符号链接文件 l
S_ISSOCK(st.st_mode)为真则表示该文件是 套接字文件 s
st_mode还包含了文件的权限位,可以是如下代码来解析
f(st.st_mode & S_IRUSR) //user有read的权限
else //没有read的权限
...
S_IWUSR, S_IXUSR
S_IRGRP, S_IWGRP, S_IXGRP
S_IROTH, S_IWOTH, S_IXOTH
nlink_t st_nlink; /*该文件的硬连接数*/
uid_t st_uid; /* 文件所有者的用户ID */
gid_t st_gid; /* 文件的所有者的组ID */
dev_t st_rdev; /* 设备号(假如该文件是一个设备) */
off_t st_size; /* 文件内容的大小 */
对于普通文件,就是指本身大小,文件内容的大小
对于符号链接(软链接文件),文件内容是什么?
指向的那个文件的文件名
目录文件的内容是什么呢?
目录项数组
blksize_t st_blksize; /* 块大小(与具体的硬件设备有关) */
blkcnt_t st_blocks; /* 该文件占多少块(512字节为一块) */
struct timespec st_atim; /* 最后访问“文件内容”的时间*/
struct timespec st_mtim; /* 最后修改“文件内容”的时间 */
struct timespec st_ctim; /* 最后修改“文件属性”的时间 */ inode
}
2.问题
在linux下面,目录,可以通过open打开它,“文件描述符”
但是我们在用 read(fd,) 去读一个目录的内容时,会报错。
=》在linux下不能用read去读一个目录。
目录文件里面的内容到底是什么呢?
3.目录与普通文件的区别
在linux下面,任何一个文件要存在,必须有一个inode
普通文件需要一个inode
目录文件需要一个inode
设备文件需要一个inode
…
普通文件的内容,“文件内容”
但 目录文件的内容,是什么吗?
目录下面可以包含文件,目录下面又可以包含目录。
目录文件的内容 是 目录项的数组
创建一个空目录时,系统会为它预留一个“目录项的数组”
当在这个目录下创建一个文件或目录时,就会填充一个目录项
到“目录项数组中去”。
目录项到底是什么呢?
struct dirent
我们要想知道一个目录下面的层次关系,必须要去读目录项!!!
4.目录操作
(1)打开一个目录
opendir
在linux中用结构体DIR来描述符一个已经打开的目录,
至于这个结构体中的具体的内容,“透明”,DIR这个结构体到底
包含了什么成员变量,我们无需关心。
DIR * 表示一个成功打开的目录。
头文件
#include <sys/types.h>
#include <dirent.h>
函数功能
打开指定的目录
函数原型
DIR *opendir(const char *name);
函数参数
const char *name //要打开的目录的名字
函数返回值
成功: 返回一个DIR的指针
失败: 返回NULL,同时errno被设置
函数功能
打开指定的目录
函数原型
DIR *fdopendir(int fd);
函数参数
int fd //要打开的那个目录的文件描述符,
意思就是说,在调用fdopendir之前,先要open一下。
函数返回值
成功: 返回一个DIR的指针
失败: 返回NULL,同时errno被设置
(2)读一个目录中的目录项
readdir
readdir用来从dirp指向的目录中,返回下一个目录项(struct dirent)的指针。
一个目录里面有多个目录项(一个文件或子目录就会对应一个目录项),每调用一次
readdir就会返回下一个目录项的指针。知道返回NULL。
在linux中,目录项结构体如下:
struct dirent {
ino_t d_ino; /* Inode的编号 */
off_t d_off; /* 目录项的偏移*/
unsigned short d_reclen; /* 该结构体的长度 */
unsigned char d_type; /* 该目录项指向的那个文件的类型 */
char d_name[256]; /* 该目录项指向的文件的名字 */
};
该结构体中的成员变量,只有d_ino,d_name是所有linux系统都支持的!!!!
如果你想让你的代码具有可移植性,该结构体代码中,只能用d_ino,d_name这
两个成员变量。
头文件
#include <dirent.h>
函数功能
读取一个目录的目录项
函数原型
struct dirent *readdir(DIR *dirp);
函数参数
DIR *dirp //表示想要从哪个目录中读取下一个目录项
函数返回值
成功: 返回下一个目录项的指针,如果读完了,则返回NULL。
失败: 返回NULL,同时errno被设置
(3)关闭一个目录
closedir
头文件
#include <sys/types.h>
#include <dirent.h>
函数功能
关闭一个指定的目录
函数原型
int closedir(DIR *dirp);
函数参数
DIR *dirp //指向要关闭的目录的DIR结构体
函数返回值
成功: 返回0
失败: 返回-1,同时errno被设置