文章目录
- 系统调用
- 函数分类
- 系统编程概述
- 系统调用概述
- **类UNIX系统的软件层次**
- 用户态和内核态
- 系统调用与库函数的关系
- 文件操作符
- 概述
- 文件磁盘权限
- 系统调用之文件操作
- open:打开文件
- close:关闭文件
- write:写入
- read:读取
- 文件状态
- fcntl 函数
- stat 函数
- st_mode的值
- 示例 1:
- 示例 2:只写和追加
- 示例 3: 写文件
- 示例 4: 读文件
- 文件夹操作
- opendir
- readdir
- 示例:遍历文件夹
- closedir
系统调用
函数分类
- 库函数
- 系统提供的,不能直接访问内核的 如fopen
- 系统调用
- 系统提供的,可以直接访问内核 如 open
- 自定义函数
- 自己编写的
注意
man命令:查看帮助手册
-
章节1 查命令 例: man 1 ls
-
章节2 系统调用 man 2 open
-
章节3 库函数 man 3 fopen
系统编程概述
操作系统的职责
操作系统用来管理所有的资源,并将不同的设备和不同的程序关联起来。
什么是 Linux 系统编程
在有操作系统的环境下编程,并使用操作系统提供的系统调用及各种库,对系统资源进行访问。
学会了C 语言再知道一些使用系统调用的方法,就可以进行Linux 系统编程了,
简单说 就是使用软件操控系统硬件以及资源 比如 灯亮…
系统调用概述
系统调用是操作系统提供给用户程序的一组“特殊”函数接口。
类UNIX系统的软件层次
用户态和内核态
引入
> CPU 指令是可以直接操作硬件的,要是因为指令操作的不规范,造成的错误是会影 响整个 计算机系统 的。好比你写 一个程序,但是因为你对 硬件操作 不熟悉,出现 问题,那么影响范围是多大?是整个计算机系统,操作系统内核、及其其他所有正在运 行的程序,都会因为你操作失误而受到不可挽回的错误,那么你只有重启整个计算机才 行 > 而对于 硬件的操作 是非常复杂的,参数众多,出问题的几率相当大,必须及其谨 慎的进行操作,这对于个人开发者来说是个艰巨的任务,同时个人开发者在这方面也是 不被信任的。所以 操作系统内核 直接屏蔽了个人开发者对于硬件操作的可能. > 这方面 系统内核 对 硬件操作 进行了封装处理,对外提供标准函数库,操作更简 单、更安全。比如 我们要打开一个文件,C标准函数库中对应的是fopen(),其内部封 装的是内核中的系统函数open() > 因为这个需求,硬件设备商直接提供了硬件级别的支持,做法就是对 CPU 指令设置了 权限,不同级别的权限可以使用的 CPU 指令是有限制的。以 Inter CPU 为例, Inter 把 CPU 指令操作的权限划为4级: ring 0 ring 1 ring 2 ring 3 > 其中 ring 0 权限最高,可以使用所有 CPU 指令,ring 3 权限最低,仅能使用 常规 CPU 指令,这个级别的权限不能使用访问硬件资源的指令,比如 IO 读写、网卡 访问、申请内存都不行,都没有权限 > Linux 系统内核采用了:ring 0 和 ring 3 这2个权限 > ring 0:内核态,完全在 操作系统内核 中运行,由专门的 内核线程 在 CPU 中 执行其任务 > ring 3:用户态,在 应用程序 中运行,由 用户线程 在 CPU 中执行其任务 > Linux 系统中所有对硬件资源的操作都必须在 内核态 状态下执行,比如 IO 的 读写,网络的操作
区别:
> 1,用户态的代码必须由 用户线程 去执行、内核态的代码必须由 内核线程 去执行 > 2,用户态、内核态 或者说 用户线程、内核线程 可以使用的资源是不同的,尤体现在 内存资源上。Linux 内核对每一个进程都会分配 4G 虚拟内存空间地址 用户态: --> 只能操作 0-3G 的内存地址 内核态: --> 0-4G 的内存地址都可以操作,尤其是对 3-4G 的高位地址必须由 内核态去操作,因为所有进程的 3-4G 的高位地址使用的都是同一块、专门留给 系统 内核 使用的 1G 物理内存 > 3.所有对 硬件资源、系统内核数据 的访问都必须由内核态去执行
如何切换内核态?
通过软件中断
软件中断与硬件中断
> 软件中断 软件中断是由软件程序触发的中断,如系统调用、软中断、异常等。软件中断不是硬件设备触发的,而是由软件程序主动发起的,一般用于系统调用、进程切换、异常处理等任务。软件中断需要在程序中进行调用,其响应速度和实时性相对较差,但是具有灵活性和可控性高的特点。 如程序中出现的内存溢出,数组下标越界等 > 硬件中断 硬件中断是由硬件设备触发的中断,如时钟中断、串口接收中断、外部中断等。当硬件设备有数据或事件需要处理时,会向CPU发送一个中断请求,CPU在收到中断请求后,会立即暂停当前正在执行的任务,进入中断处理程序中处理中断请求。硬件中断具有实时性强、可靠性高、处理速度快等特点。 如当点击按钮扫描系统高低电频时等
系统调用与库函数的关系
注意:
系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。当运行内核代码时,CPU工作在内核态,在系统调用发生前需要保存用户态的栈和内存环境,然后转入内核态工作。系统调用结束后,又要切换回用户态。这种环境的切换会消耗掉许多时间。
文件操作符
概述
> 文件描述符是一个非负整数,代表已打开的文件。 每一个进程都会创建一张文件描述符表 记录的是当前进程打开的所有文件描述符。 每一个进程默认打开三个文件描述符: 0(标准输入设备scanf) 1(标准输出设备printf) 2(标准错误输入设备perror)。 新打开的文件描述符 为最小可用文件描述符
扩展
ulimit是一个计算机命令,用于shell启动进程所占用的资源,可用于修改系统资源限
制。使用ulimit命令用于临时修改资源限制,如果需要永久修改需要将设置写入配置文
件/etc/security/limits.conf。
ulimit -a 查看open files打开的文件最大数。
ulimit -n 最大数 设置open files打开的文件最大数。
文件磁盘权限
第一位d 说是 文件还是文件夹
2~4位说明 所有者权限
5~7位说明 同组用户权限
8~10位说明其他用户权限
r 4
w 2
x 1
只读 4
只写 2
只执行 1
可读可写 6
可读可执行 5
可读可写可执行 7
系统调用之文件操作
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_RDWR 以可读、可写的方式打开 可选项 O_CREAT 文件不存在则创建文件,使用此选项时需使用 mode 说明文件的权限 O_EXCL 如果同时指定了 O_CREAT,且文件已经存在,则打开,如果文件不存在则新建 O_TRUNC 如果文件存在,则清空文件内容 O_APPEND 写文件时,数据添加到文件末尾 O_NONBLOCK 对于设备文件, 以 O_NONBLOCK 方式打开可以做非阻塞I/O mode:文件在磁盘中的权限 格式: 0ddd d的取值:4(可读),2(可写),1(可执行) 第一个d:所有者权限 第二个d:同组用户权限 第三个d:其他用户权限 如果需要可读可写就是6,可读可执行5等 如: 0666:所有者可读可写,同组用户可读可写,其他用户可读可写 0765:所有者可读可写可执行,同组用户可读可写,其他用户可读可执行 返回值: 成功:得到最小可用的文件描述符 失败:-1
技巧:
- 操作已有文件使用两参
- 新建文件使用三参
close:关闭文件
0 代表标准输入(stdin),
1 代表标准输出(stdout),
2 代表标准错误输出(stderr),
例如: close(0);表示 标准输入禁用
所需头文件 #include<unistd.h> 函数 int close(int fd); 参数 关闭的文件描述符 返回值 成功:0 失败:-1,并设置errno
write:写入
所需头文件 #include <unistd.h> 函数 ssize_t write(int fd, const void *buf, size_t count); 参数 fd:写入的文件描述符 buf:写入的内容首地址 count:写入的长度,单位字节 返回值 成功:返回写入的内容的长度,单位字节 失败:-1
read:读取
所需头 #include <unistd.h> 函数 ssize_t read(int fd,void *buf,size_t count); 参数: fd:文件描述符 buf:内存首地址 count:读取的字节个数 返回值: 成功:实际读取到的字节个数 失败:-1
读取完的依据:
返回值 小于 设定的每次读取的字节数
文件状态
fcntl 函数
作用: 针对已存在的文件描述符设置阻塞状态 语法: 所需头文件: #include <unistd.h> #include <fcntl.h> 函数: int fcntl(int fd, int cmd, ... /* arg */); 功能: 改变已打开的文件性质,fcntl 针对描述符提供控制。 参数: fd:操作的文件描述符 cmd:操作方式 arg:针对 cmd 的值,fcntl 能够接受第三个参数 int arg 返回值: 成功:返回某个其他值 失败:-1 fcntl 函数有 5 种功能: 1) 复制一个现有的描述符(cmd=F_DUPFD) 2) 获得/设置文件描述符标记(cmd=F_GETFD 或 F_SETFD) 3) 获得/设置文件状态标记(cmd=F_GETFL 或 F_SETFL) 4) 获得/设置异步 I/O 所有权(cmd=F_GETOWN 或 F_SETOWN) 5) 获得/设置记录锁(cmd=FGETLK, F_SETLK 或 F_SETLKW)
stat 函数
作用:
获取文件状态信息
所需头#include <sys/types.h> #include <sys/stat.h> #include <unistd.h> 函数 int stat(const char *path, struct stat *buf); int lstat(const char *pathname,struct stat *buf); 参数 1参:文件地址 2参:保存文件信息的结构体 返回值 0:成功 -1:失败 注意: 当文件是一个符号链接时 lstat返回的是该符号链接本身的信息。 stat返回的是该链接指向的文件的信息。【重要】 struct stat { dev_t st_dev; //文件的设备编号 ino_t st_ino; //节点 mode_t st_mode; //文件的类型和存取的权限 nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为 1 uid_t st_uid; //用户 ID gid_t st_gid; //组 ID dev_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_mode的值
示例 1:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
// O_TRUNC 覆盖清空 O_RDONLY 只读 O_WRONLY 只写
int f01 = open("./a.txt", O_RDONLY | O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (f01 < 0)
{
printf("打开失败!\n");
return 0;
}
printf("end \tf01=%d\n", f01); // 3 因为0 1 2 被占用
int flag = close(f01);
printf("flag = %d\n", flag);
if (flag < 0)
{
printf("关闭失败\n");
}
else
{
printf("关闭成功\n");
}
return 0;
}
示例 2:只写和追加
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
// 关闭标准输入流
close(0);
// 权限是 只写 和追加 方式 打开
int fd = open("./a.txt", O_WRONLY | O_APPEND );
printf("fd = %d\n",fd); //此时打印的fd是0,证明返回的是最小可用描述符
char *str = "你好\n";
write(fd, str, strlen(str));
close(fd); // 关闭文件
return 0;
}
示例 3: 写文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char *str = "hello , 系统调用\n"; // 此处中文占了3字节 但是一般中文占2~4 具体视情况而定
//参数1: 写入的文件描述符
//参数2: 写入的内容首地址
//参数3: 写入的长度,单位字节
//成功:返回写入的内容的长度,单位字节 失败:-1
int t = write(1,str,strlen(str)); // strlen 测长度 不算 \0
printf("xxz = %d\n",t);
return 0;
}
示例 4: 读文件
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
char str[50] = {0};
// 参数一: 文件描述符
// 参数二: 内存首地址
// 参数三: 读取的字节个数
// 返回值: 成功:实际读取到的字节个数 失败:-1
read(0, str, 50); // 0 标准输入
printf("num = %s\n",str);
return 0;
}
文件夹操作
遍历文件夹很重要
opendir
打开目录
> 作用:打开目录 opendir
所有头文件:
#include <sys/types.h>
#include <dirent.h>
函数:
DIR *opendir(const char *name);
参数:
name:目录名
返回值:
成功:返回指向该目录结构体指针(DIR *)
失败:NULL
DIR:中文名称句柄,其实就是目录的结构体指针
readdir
读取目录
> 作用:
读取目录
所需头文件
#include <dirent.h>
函数
struct dirent *readdir(DIR *dirp);
参数:
dirp:opendir 的返回值
返回值:
成功:目录结构体指针
失败:NULL
注意:一次读取一个文件。
相关结构体说明:
struct dirent
{
ino_t d_ino; // 此目录进入点的 inode
off_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_FIFO 这是一个命名管道(FIFO)。
DT_LNK 这是一个符号链接。
DT_REG 这是一个常规文件。
DT_SOCK 这是一个UNIX域套接字。
DT_UNKNOWN 文件类型未知。
示例:遍历文件夹
#include <stdio.h>
#include <string.h>
// 遍历 家目录 文件夹
// 打开文件夹所需头文件
#include <sys/types.h>
#include <dirent.h>
void blDIR(char *dirPATH)
{
DIR *d = opendir(dirPATH); // 打开文件夹
//返回指向该目录结构体指针(DIR *)
if (d == NULL)
{
printf("打开失败\n");
return 0;
} // 打开成功 使用递归开始遍历
while (1)
{
struct dirent *d2 = readdir(d);
if (d2 == NULL)
{
break;
}
if (d2->d_type == DT_DIR)
{
if (strcmp(d2->d_name,".") == 0 || strcmp(d2->d_name,"..") == 0)
{//遇到文件有. 和.. 的 就暂不执行本次循环
continue; // 跳出本次循环
}
char path[500] = {0};
//追加操作 将dirent 内容追加到 path尾部
strcat(path,dirPATH);
strcat(path,"/");
strcat(path,d2->d_name);
// printf("=======\n");
printf("%s\n",path);
blDIR(path);
}
else if (d2->d_type == DT_REG) // 类型要是文件
{
printf("%s\n",d2->d_name);
}
}
closedir(d);// 关闭文件d
}
int main(int argc, char const *argv[])
{
blDIR("/home/xxz/");
return 0;
}
closedir
关闭目录
> 作用:关闭目录 closedir
所需头文件
#include <sys/types.h>
#include <dirent.h>
函数
int closedir(DIR *dirp);
参数:
dirp:opendir 返回的指针
返回值:
成功:0
失败:-1