前言
在linux系统中,内核把一切内容都以文件的形式来表示 ----- 一切皆文件。所有的内容使用linux创建的虚拟文件系统vfs进行管理,将所有的设备用文件的概念封装起来。
在应用中使用统一的接口,实现队底层不同文件类型的掉用,通过vfs中的file_opeartion结构体,提供了一个统一的函数集合,对不同类型文件的支持(某一种文件或设备能够实现的操作都是这个结构体的子集)。
内核实现不同类型文件的区别细节,通过内核提供统一接口进行操作。
- 两种文件操作方式:既可以使用由操作系统直接提供的系统调用函数接口,也可以使用标准C库提供的函数调用。
- 系统调用:由内核提供的操作系统的接口函数功能,与内核相关,不同系统可能调用不同。系统调用是以稳定、健壮为主;系统调用的功能比较单一,但在应用时,可能需要更加丰富的功能,因此开发了很多库,比如标准C库,在C库中很多功能都是对系统调用的封装。
- 库函数调用:由标准委员会进行设计,只要使用对应的语言就可以使用的函数调用。
- 应用程序不能直接访问内核,操作系统通过系统调用接口,好让应用程序通过它们使用内核提供的各种服务。
在系统调用和C库中,都有一组函数专门用于文件操作的:比如打开文件、关闭文件、读写文件。如果是系统调用 ----- 文件IO(系统IO);如果是库函数调用 -----标准IO。
1、文件IO
文件IO(系统IO函数)
与操作系统相关。
1.1、打开文件
打开一个文件,就获取到这个文件的访问标识(文件描述符---在内核管理的打开文件顺序表的下标)
功能 | 打开一个指定的文件并获取文件描述符,或创建一个新文件。 |
头文件 | #include #include #include |
函数原型 | int open(const char *pathname, int flags); |
int open(const char *pathname, int flags, mode_t mode); | |
参数 | const char * pathname:指针,字符串的首地址,字符串为即将打开的文件名 |
int flags:打开方式
| |
mode_t mode:创建文件的权限
| |
返回值 | 成功:返回打开文件的文件描述符(int);失败:返回 -1。 |
1.2、关闭打开的文件
功能 | 通过文件描述符,关闭打开的文件 |
头文件 | #include |
函数原型 | int close(int fd); |
参数 | int fd:文件描述符,表示即将关闭的文件 |
返回值 | 成功:返回0;失败:返回-1。 |
1.3、读写打开的文件
1.2.1、读取文件内容
功能 | 从指定文件中读取数据 |
头文件 | #include |
函数原型 | ssize_t read(int fd, void *buf, size_t count); |
参数 | int fd:文件描述符,指定读取的文件,从fd文件中读取。 |
void * buf:读取的数据存储的地址,自定义存储位置。 | |
size_t count:想从文件中读取对应的字节数。 | |
返回值 | 成功:返回实际读取的字节数,0表示当前已经读取到末尾;失败:返回-1。 |
1.2.2、写入文件内容
功能 | 往指定文件中写入数据 |
头文件 | #include |
函数原型 | ssize_t write(int fd, void *buf, size_t count); |
参数 | int fd:文件描述符,指要写入的文件,写入fd文件 |
void * buf:要写入的数据存储的地址,自定义存储位置,把空间的数据写入。 | |
size_t count:要写入的字节数。 | |
返回值 | 成功:返回写入的字节数;失败:返回-1。 |
1.4、文件偏移位置
在读写文件的时候,为了保证每次都是从当前的下一个位置操作,便有个偏移量的概念,这个偏移位置,可以获取,也可以人为调整。
功能 | 调整、设置文件偏移量 |
头文件 | #include #include |
函数原型 | off_t lseek(int fd, off_t offset, int whence); |
参数 | int fd:要调整位置偏移量的文件描述符 |
off_t offset:新的偏移量相对基准点的偏移量 | |
int whence:基准点 SEEK_SET:文件开始处 SEEK_CUR:当前位置 SEEK_END:文件末尾处 | |
返回值 | 成功:返回新位置距离文件开始处的偏移量;失败:返回-1。 |
1.5、文件属性
在操作文件时,经常需要获取文件的属性,比如类型、权限、大小、所有者等等。
功能 | 获取文件的元属性 |
头文件 | #include #include #include |
函数原型 | 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:指针字符串地址,字符串存储文件名。 |
int fd:要获取属性的文件的描述符 | |
const char *pathname:要获取属性的文件或文件路径(用双引号包括)。 | |
struct stat *statbuf:结构体指针,当调用函数时,把属性存储到这个地址中(属性结构体)。 | |
返回值 | 成功:返回0;失败:返回-1。 |
属性结构体:
struct stat
{
dev_t st_dev;//文件所在存储器的设备号
ino_t st_ino;//每个文件的唯一编号(inode号)
mode_t st_mode;//文件类型和文件权限. inode 7:
//文件类型:12-15bit, st_mode & 0170000(八进制数) 为文件类型.
//文件权限:0-8bit. st_mode & 0777(八进制数) 为文件权限.
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;//所占块数
struct timespec st_atim;//最后访问时间
struct timespec st_mtim;//最后修改时间
struct timespec st_ctim;//最后属性修改时间
#define st_atime st_atim.tv_sec//tv_sec 表示从1970年到当前的秒数
#define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec
};
2、标准IO
标准IO是C库的一部分,实际上是对文件IO的封装,提供了更加丰富的操作方式。
2.1、打开文件
功能 | 打开文件,获取文件流指针(来操作表示一个打开文件) |
头文件 | #include |
函数原型 | FILE * fopen(const char * pathname, const char * mode); |
参数 | const char * pathname:指针,字符串的首地址;表示即将打开的文件吗(路径)的文件字符串 |
const char * mode:指针,字符串首地址;表示打开的方式 | |
打开方式参数 | "r":只读方式打开文件,同时文件操作在文件开始位置。 |
"r+":读写方式打开文件,同时文件操作在文件开始位置。 | |
"w":只写方式打开文本文件,如果文件存在,则清空文件;文件不存在则创建文件。在文件开始位置进操作。 | |
"w+":读写方式打开文本文件,如果文件存在,则清空文件;文件不存在则创建文件。在文件开始位置进操作。 | |
"a":追加写方式打开文本文件,如果文件不存在就创建,在文件末尾进行操作(定位到文件末尾)。 | |
"a+":读且追加写方式打开文本文件,如果文件不存在就创建,写在文件末尾进行操作(定位到文件末尾)。 | |
"b":以二进制方式打开文件 | |
返回值 | FILE * 成功:返回打开的文件指针;失败:返回NULL。 |
返回的文件指针,是一种FILE结构体的地址(指向FILE类型的指针),FILE结构体就是描述打开文件的各种信息。 |
- 文件流:就是描述这个文件的各种信息内容,能够按照流的方式进行读写。
- 文件流指针:描述这个信息(文件信息)的地址。
2.2、关闭打开的文件
功能 | 关闭打开的文件 |
函数原型 | int fclose(FILE *stream); |
参数 | FILE *stream:文件指针,即将关闭的文件 |
返回值 | 成功:返回0,;失败:返回EOF(-1)。 |
2.3、读写打开的文件
在程序执行时,默认会打开三个文件:
设备 | 文件指针 | 文件描述符 |
标准输入(键盘) | FILE * stdin | 0 |
标准输出(终端显示器) | FILE * stdout | 1 |
标准出错输出(终端显示器) | FILE * stderr | 2 |
标准IO函数读写接口:
2.3.1、读写单个字符
每次一个字符的读写标准IO接口函数。
2.3.1.1、文件读取
功能 | 获取指定文件中的一个字符 |
头文件 | #include |
函数原型 | int getchar(void);----从键盘文件中获取一个字符 |
int fgetc(FILE *stream); | |
int getc(FILE * stream); | |
参数 | FILE *stream:文件指针,指定读取字符的文件 |
返回值 | 成功:返回读取到的字符(以int类型返回);失败:返回EOF,表示本次读取到文件末尾或读取失败。 |
2.3.1.3、文件写入
功能 | 将一个字符写入指定的文件 |
头文件 | #include |
函数原型 | int putc(int c, FILE * stream); |
int fputc(int c, FILE * stream); | |
int putchar(int c); == fputc(c, stdout) --- 往终端文件上写入FILE | |
参数 | int c:要写入的字符 |
FILE * stream:文件指针,要写入的文件 | |
返回值 | 成功:返回写入的字符的值;失败:返回EOF |
2.3.2、读写一行字符
每次一行的读写标准IO函数
2.3.2.1、文件读取
功能 | 从文件中读取最多一行数据 |
头文件 | #include |
函数原型 | char * fgets(char * s, int size, FILE * stream); |
char * gets(char * s); --- 从stdin文件中读取字符放入s中,无大小限制。 | |
参数 | char * s:自定义读取数据的存储空间首地址(从文件中读取的数据存储的地址) |
int size:自定义空间的存储大小。最多读取size - 1个字符。(会在读取完的下一个位置加上'\0'以便当作字符串处理) | |
FILE *stream:文件指针,指定读取字符的文件 | |
返回值 | 成功:返回存储空间首地址;失败:返回NULL,表示本次读取失败或当前读取到文件末尾。 |
2.3.2.2、文件写入
功能 | 将数据写入指定文件 |
头文件 | #include |
函数原型 | int fputs(const char * s, FILE * stream);----直到'\0'结束。 |
int puts(const char * s); == fputs(s, stdout); --- 将字符串s写入终端文件。 | |
参数 | const char * s:自定义写入数据,把s首地址的数据写入文件(将s首地址开始的每一个字符写入文件,直到s字符串的字符为'\0')。 |
FILE * stream:文件指针,要写入的文件。 | |
返回值 | 成功:返回非负整数;失败:返回EOF。 |
2.3.3、读取若干数据块
每次读取若干数据块
2.3.3.1、读取文件
功能 | 从指定文件中读取若干个数据块 |
头文件 | #include |
函数原型 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); |
参数 | void * ptr:指针,读取数据存放的首地址(自定义的存储位置),须在末尾自己加入'\0'。 |
size_t size:整数,要读取每个数据块的大小。 | |
size_t nmemb:整数,读取的数据块的个数。 | |
FILE *stream:即将被读取数据的文件的指针 | |
返回值 | 成功:返回读取的数据块的个数;失败:返回0,表示当前读取已经读到文件末尾,或读取错误。 |
2.3.3.2、写入文件
功能 | 将若干数据写入指定文件 |
头文件 | #include |
函数原型 | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); |
参数 | void * ptr:指针,要写入数据的首地址(自定义的存储位置)。 |
size_t size:整数,要写入每个数据块的大小。 | |
size_t nmemb:整数,写入的数据块的个数。 | |
FILE *stream:要写入的文件指针 | |
返回值 | 成功:返回写入的数据块的个数;失败:返回0 |
由于fread、fwrite是严格按照数据块和数据大小来处理,而不会对数据格式做处理,数据中的特殊字符'\0'、'\n'不会受到影响,因此可以用于二进制文件的操作。
2.3.4、文件操作判断
功能 | 判断是否到达文件末尾 |
函数原型 | int feof(FILE * stream); |
参数 | FILE * stream:判断的文件 |
返回值 | 如果已经到达文件末尾,返回1(真),否则返回0(假)。 |
功能 | 判断文件操作是否出错 |
函数原型 | int ferror(FILE * stream); |
参数 | FILE * stream:判断的文件 |
返回值 | 如果文件遇到错误,返回1(真),否则返回0(假)。 |
2.4、标准IO的缓冲机制
使用标准IO读写操作时,在打开文件时会创建缓冲区,用于读写文件。
- 读:从内核中读取一段数据到缓冲区,应用程序读操作就从缓冲区读取。
- 写:写入文件时,先把数据写入缓冲区,当缓冲区满足一定条件时,才会写入文件中去。
- 三种缓冲机制:
- 无缓冲:数据直接写入文件。--- stderr。
- 行缓冲:只要在缓冲区遇到 '\n' 或缓冲区满,就把缓冲区数据刷新到文件。--- stdin、strout。
- 全缓冲:只要缓冲区满,就把缓冲区数据刷新到文件。--- 其它用标准IO打开的文件。
- 主动刷新缓冲函数
功能 | 主动刷新缓冲 |
函数原型 | int fflash(FILE * stream); |
参数 | FILE * stream:要刷新缓冲的文件 |
返回值 | 成功:返回0,失败:否则返回非0。 |
3、目录操作
对目录进行的操作。
3.1、打开目录
功能 | 打开一个存在的目录,获取目录指针,可以获取目录项。 |
头文件 | #include #include |
函数原型 | DIR *opendir(const char *name); |
参数 | const char *name:要打开的目录或目录路径(用双引号包括)。 |
返回值 | 成功:返回目录指针;失败返回NULL指针 |
3.2、读取目录
功能 | 在打开的目录中,读取一个目录项。 |
头文件 | #include |
函数原型 | struct dirent *readdir(DIR *dirp); |
参数 | DIR *dirp:目录指,读取目录项的目录指针。 |
返回值 | 成功:返回目录项结构体(目录中一个文件的基本信息)地址;失败:返回NULL,表示读取失败或读取到目录末尾结束。 |
struct dirent
{
ino_t d_ino;//inode号
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[256];//文件名
};
4、多文件与库的制作
4.1、GCC编译
GCC编译一个程序的流程:
1、预处理
- 处理以 # 开头的语句,不进行语法检查。
- 展开头文件,将头文件中的内容复制到 .c 文件中去。
- 处理宏替换(#define中的内容)。
- 预处理后的后缀名:.i 表示经过预处理的文件
- -E:只进行预处理
2、编译(C编译器)
- 将C语言程序 在转换为 汇编语言 要进行语法检查。
- 编译后的后缀名:.s 表示汇编文件
- -S:将预处理的文件进行编译。
3、汇编(汇编器)
- 将 .s 汇编代码通过汇编器编译为机器指令(二进制)。
- 机器指令文件后缀名:.o 表示二进制机器指令码文件。
- -c:将目标文件编译为二进制机器指令。
4、链接(链接器)
- 将多个二进制文件机器码文件(也可能和库文件) 进行合并链接,形成一个完整的程序文件。
- 在进行多文件编译时,在编译步骤中,只要链接步骤才能合并在一起,之前的步骤都是单个文件独立进行编译,需要对使用的其他文件内容进行声明;通过头文件声明,在编译时进行预处理就会把 头文件 的内容复制到源文件。
- 头文件声明:
#ifndef x1_H
#define x1_H
//声明内容
#endif
4.2、多文件
在工程程序中,通常一个完整的项目不会使用一个文件来编写所有的操作功能,而是按照分类的思想方法,把相关性的操作写在一个文件中,通过多个文件组合形成一个完整的工程。
- 制作步骤:
- 分别创建并编写多个代码文件。
- 声明:在编译时,如果用到了其他文件的内容,需要对其他文件的内容进行声明。
- 编译:gcc 文件1.c gcc 文件2.c ……
4.3、库的制作
库:从本质上来说是一种可执行代码的二进制格式,可以被载入执行。
4.3.1、库的分类
-
静态库
- 可以直接在编译时就加入到程序中,进行使用。
静态库的制作:
1、把程序文件编译为二进制文件,只是编译不链接。
- gcc -c xxx.c -o xxx.o
2、 生成静态库
- ar -cr libxxx.a xxx.o
- libxxx.a 就是生成的静态库
使用静态库:
- -L:库的路径
- -l(小写L):链接库名
- -I(大写i):库的头文件路径
- 在编译程序时使用。
- 在编译我们自己的程序时,添加库。
- gcc xx1.c xx2.c ... -L 库路径 -l 库名(可直接写xxx) -I 头文件路径
-
动态库(共享库)
- 动态库相比于静态库生成的可执行程序体积小、效率高;在运行程序时需要依赖动态库。
- 在编译时,只是将库中的函数符号表和程序进行编译链接。
- 动态库:直接在运行时才加入程序中,进行使用。
- 动态库的制作:
- gcc -fPIC -shared xxx.c -o libxxx.so
- -fPIC:忽略文件位置。
- -shared:生成共享库。
- gcc -fPIC -shared xxx.c -o libxxx.so
- 使用动态库:
- 编译:gcc xx1.c xx2.c ... -L 库路径 -l 库名(可直接写xxx) -I 头文件路径
- 运行:运行时还需要动态库才能运行。ldd 执行文件名(查看动态库)
- 指定动态库,在运行时才能够找到动态库。
- 方法1:将自己的库拷贝到系统lib下
- cp *.so /lib
- 方法2:通过指令 LD_LIBRARY_PATH(临时生效)
- export LD_LIBRARY_PATH=库的绝对路径
- 方法3:通过系统的配置文件指定库的路径。
- 配置路径:/etc/ld.so.conf.d目录下,新加文件或在已有文件中添加路径。
- 使配置生效:sudo ldconfig
5、进程与线程
5.1、进程
5.1.1、进程的概念
一个程序文件,只是一堆待执行的代码和比分待处理的数据。程序只有加载到内存中,然后让CPU逐条执行其代码,才会根据代码内容做出相应的动作,才形成一个动态的进程。
- 进程:正在执行中的程序,进程就是程序的一次执行,是程序执行中动态变化的过程,是动态的;程序只是这一系列动作的指令操作文本,是静态的。
- 进程控制块(PCB):内核管理进程时,会为每个进程都创建一个进程控制块,作为内核管理进程的依据(task_struct)。
- 记录了进程执行时所有的资源;随着进程的执行,资源是动态变换的(各种资源被申请与释放)。
进程包含执行时的所有资源变化,而程序没有。
5.1.2、进程的执行
- CPU的分时机制:进程在操作系统中调度是切换运行的,每个进程都有一个CPU时间片(一个进程在CPU上运行的时间段),在CPU上时间片运行完毕后切换到下一个进程。
- 进程执行:进程的执行是由另一个进程创建,即有父进程。
- init进程:系统运行的第一个进程。
- 在进程中,通过pid(进程id)号来识别每个执行的进程。
- 查看进程的情况:ps、top(动态查看)
- 进程的状态:开始、休眠、等待、暂停、消亡
- 进程是动态的执行过程,进程在执行时会有很多运行的状态。
- 就绪态:进程在等待队列中排队,占用cpu时间就允许,也可以占用cpu正在运行占用cpu或等在cpu,根据内核的各种调度方式,选择就绪的进程进入cpu执行,执行时间片对应的时间。
- 僵尸态:程序进程已经执行结束,不再执行,资源等待回收;进程结束信息都封存在状态信息中。
- 死亡态:父进程进行回收子进程的资源,查看子进程的结束信息。当父进程已经消亡,子进程的回收由init进程管理-----子进程会被收养,由收养的进程进行回收。
- 睡眠态:处于就绪态,但是缺少某些资源不可执行,会睡眠等待,当由对应资源时就可执行。
- 暂停/停止态:当进程收到暂停信号时,进程的执行就被停止,此时就叫做暂停态,在该状态下进程不参与调度,但系统资源不会释放,放在内核的等待队列,当有激活信号(SIGCONT),就从等待队列移动到就绪队列继续执行。
5.1.3、进程相关函数操作
5.1.3.1、进程的创建
功能 | 程序创建一个新的进程(执行函数就额外创建一个新的进程);在创建子进程时,会拷贝父进程的所有资源到子进程中,用于子进程执行;子进程从fork()的返回值开始执行。 |
头文件 | #include #include |
函数原型 | pid_t fork(void); |
返回值 | 成功:父进程中返回子进程的pid,子进程中返回0;失败:返回 -1。 |
- fork函数作用创建子进程,把进程内容完全复制一份给进程进行执行。
- fork()成功,分别会在父进程和子进程返回值,但返回值不同,可以根据返回值设置子进程和父进程执行特定的代码内容。
- 子进程会从fork()的下一句开始执行。
- 父子进程是相互平等的,执行的次序是随机的;符字进程是相互独立的,子进程完全拷贝父进程的内存空间,子进程有自己的内存空间,内存使用是独立开的,互不影响。
- fork创建子进程,子进程执行内容于父进程完全一致,只能在程序中根据pid值选择执行。
5.1.3.2、进程的结束
- 主函数return结束进程。
- 给进程发送结束信号 :kill + 进程号
- 调用进程结束函数:
功能 | 结束当前进程 |
头文件 | #include |
#include | |
函数原型 | void exit(int status); |
void _exit(int status); | |
参数 | int status:整数 & 0xff 作为当前进程的结束状态;这个值存于pcb,等待父进程回收。 |
备注 | 在进程的任意位置调用都会结束当前进程。 结束状态::正常结束:0;异常:非0 |
exit:在结束时会刷新缓冲区;_exit在结束时不会刷新缓冲区。 |
父进程等待子进程结束回收子进程的资源状态。
功能 | 阻塞等待子进程结束,回收子进程资源,获取子进程的结束状态 |
头文件 | #include xxxxx |
函数原型 | pid_t wait(int * wstatus); |
参数 | int * wstatus:指针,接收子进程状态的地址(获取状态存入地址) |
返回值 | 成功:返回等待回收的子进程pid;失败:返回 -1。 |
功能 | 等待指定的一个子进程结束,获取结束状态 |
头文件 | #include #include |
函数原型 | pid_t waitpid(pid_t pid, int * wstatus, int options); |
参数 | pid_t pid:子进程pid。 小于-1表示等待组id的绝对值为pid的进程任意子进程结束;
|
int * wstatus:指针,接收子进程状态的地址(获取状态存入地址) | |
int options:选项 WNOHANG:非阻塞,查看子进程是否结束,结束就回收,没有结束不会等待。 0:阻塞,没结束则等待,结束则回收。 | |
返回值 | 成功:返回等待回收的子进程pid;如果是非阻塞且没有子进程结束则返回0。 失败:返回 -1。 |
5.1.3.3、进程其它函数
- 进程内容替换
功能 | 在进程中,替换原进程内容,改为新程序执行。 |
头文件 | #include |
函数原型 | int execl(const char *pathname, const char *arg, ... /* (char *) NULL */); |
参数 | const char * pathname:要替换程序的路径 |
参数2 - n:可变参数,字符串,用于表示程序执行的参数 | |
参数2:const char *arg:程序名 | |
参数3:const char *arg:程序名的参数 | |
参数n:表示参数结束(NULL)。 | |
返回值 | 失败:返回 -1;成功:没有返回值。 |
函数原型 | int execlp(const char *pathname, const char *arg, ... /* (char *) NULL */); |
参数 | 不是完整的路径,只需要程序名,execlp调用时会从系统路径($PATH)中查找程序。 |
功能 | 使用新的程序来替换当前进程 |
头文件 | #include |
函数原型 | int execv(const char * pathname, char * const argv[]); |
参数 | const char * pathname:替换的程序路径名 |
char * const argv[]:指针数组,数组中的每个元素为char * 类型指针,每个元素用于表示程序执行的参数列表,执行新程序的所有参数都存放到数组指针中。 | |
返回值 | 失败:返回 -1。 |
- 获取进程识别码
功能 | 获取当前进程的进程识别码 |
头文件 | #include |
函数原型 | pid_t getpid(void); |
返回值 | 失败:返回 -1;成功:没有返回值。 |
5.1.4、守护进程
- 交互进程:进程的执行有交互过程,如终端进程
- 批处理进程:指定顺序,依次执行多个进程内容,如shell脚本。
- 守护进程(精灵进程):随着系统启动而启动执行,系统关闭结束才结束进程。
- 周期性的执行某项任务或等待某个事件的进程,它的运行不依赖任何shell终端,它的生存周期较长,从开机运行到关机结束。
- 进程组:
- linux进程以组为单位,在进程组中的第一个进程为进程组组长。
- 会话:
- 管理一个或多个进程组,在会话的第一个进程组,就是会话的组长;关闭会话,会把会话中所有进程都结束关闭。
5.1.4.1、创建守护进程
1、创建子进程,退出父进程
pid_t pid = fork();
if(pid > 0)
exit(0);
2、在子进程中创建新的会话
功能 | 在进程中创建新的会话 |
头文件 | #include #include |
函数原型 | pid_t setsid(void); |
返回值 | 成功:返回新的进程(会话)ID;失败:返回 -1。 |
3、改变子进程(守护进程)的工作目录
功能 | 改变子进程的工作目录 |
头文件 | #include |
函数原型 | int chdir(const char *path); |
int fchdir(int fd); | |
参数 | const char *path:新的工作目录路径。 |
int fd:目录为一个打开的文件描述符。 | |
返回值 | 成功:返回0;失败:返回 -1。 |
4、修改子进程文件操作掩码
功能 | 修改进程文件操纵掩码 |
头文件 | #include #include |
函数原型 | mode_t umask(mode_t mask); |
参数 | mode_t mask:新掩码值。 |
返回值 | 始终成功,并返回掩码的上一个值。 |
5、关闭所有的文件描述符
功能 | 获取已经打开的文件描述符的个数 |
头文件 | #include |
函数原型 | int getdtablesize(void); |
返回值 | 获取已经打开的文件描述符的最大个数加1 |
- 通过函数 getdtablesize 获取文件描述符个
- 循环关闭文件描述符:close(0 ~ s - 1);
- time_t time(time_t *tloc);该函数计算从1970年1月1日到现在时间的秒数
- char *ctime(const time_t *time);该返回一个表示当地时间的字符串指针,字符串形式 day month year hours:minutes:seconds year\n\0。
5.1.5、进程间通信
每个进程都有自己运行的所有资源,每个进程都是独立的相互不干扰的,每个进程都是使用自己的资源空间。
5.1.5.1、管道
在内核的内存空间设置一个能够传输数据的通道,通过文件IO的操作方式操作这个通道,进行数据传输。
要进行通信的进程,一个区创建管道,另外的就链接到管道,进行通信。
- 无名管道
- 管道没有名字标识,无法在之后找到管道1文件。
- 无名管道只能用于父子进程间通信(子进程会复制父进程的所有资源)。
- 属于半双工通信或单工通信
- 管道没有名字标识,无法在之后找到管道1文件。
- 创建无名管道
功能 | 创建打开无名管道文件,得到管道的文件描述符,操作管道文件。 |
头文件 | #include |
函数原型 | int pipe(int pipefd[2]); |
参数 | int pipefd:数组地址,用于存储创建打开的文件描述符。
|
返回值 | 成功:返回 0;失败:返回 -1。 |
- 有名管道
- 管道有名字标识,创建有名管道后,在对应的目录中存在对应名字的管道文件;进程打开管道文件后,在内存空间中便使用管道文件。
- 使用方法:对有名管道的使用就是对文件的操作。
- 在磁盘中,管道文件大小始终为0;操作时,是在内存中进行操作。
- 管道有名字标识,创建有名管道后,在对应的目录中存在对应名字的管道文件;进程打开管道文件后,在内存空间中便使用管道文件。
- 创建管道文件
功能 | 创建一个有名管道文件。 |
头文件 | #include |
函数原型 | int mkfifo(const char * pathname, mode_t mode); |
参数 | const char * pathname:创建管道文件的路径名 |
mode_t mode:创建管道文件的读写权限。 | |
返回值 | 成功:返回 0;失败:返回 -1。 |
- 操作有名管道:
- 打开有名管道:int fd = open("./fifo", O_RDONLY或 O_WRONLY);
- 从管道中读取或往;管道中写入:
- 读取:read();
- 写入:write();
5.1.5.2、信号
信号是一种通知,而不是携带数据进行传输,给指定的进程产生一个通知信号。
- 对于linux内核支持的信号
17:忽略
18:恢复运行
19:暂停信号,系统的暂停信号。
20:暂停信号,由控制终端发起的暂停信号。
21:暂停
22:暂停
23:忽略
28:忽略
- 内核中的信号,都有一个设定好的默认操作,当进程接收到信号后可以执行默认操作。
- 对于进程而言,当进程接收到信号后,可以支持三种操作:
- 执行该信号的缺省操作(内核规定的信号操作)。
- 进程对该信号设置为忽略,丢弃该信号(接收到的信号什么也不会做)---SIGSTOP 和 SIGKILL 是特殊信号,不能忽略。
- 进程捕获信号,进程中接收到对应的信号,按照进程的设定操作进行。
- 对于进程而言,当进程接收到信号后,可以支持三种操作:
当向进程发送信号时,进程就会接收到对应的信号,进行处理。
- 信号操作
1、发送信号
功能 | 给指定进程发送信号 |
头文件 | #inlcude #include |
函数原型 | int kill(pid_t pid, int sig); |
参数 | pid_t pid:发送给哪个pid进程
|
int sig:要发送的信号。 | |
返回值 | 成功:返回0;失败:返回-1. |
2、信号的处理
功能 | 给指定进程发送信号 |
头文件 | #include |
函数原型 | sighandler_t signal(int signum, sighandler_t handler); |
函数指针 | typedef void (* sighandler_t)(int); |
参数 | int signum:设定哪个信号 |
sighandler_t handler:函数指针,信号的处理方式 当之后捕获到对应的信号后,调用哪个函数来处理
| |
返回值 | 成功:返回参数二的值;失败:返回SIG_ERR。 |
3、给当前进程发送信号
功能 | 给当前进程发送信号 |
头文件 | #include |
函数原型 | int raise(int sig); |
参数 | int sig:要发送的信号 |
返回值 | 成功:返回0;失败:返回非0。 |
4、挂起进程等待信号
功能 | 挂起睡眠进程等待,直到接收到信号后唤醒。 |
头文件 | #include |
函数原型 | int pause(void); |
返回值 | 成功:返回 -1;失败:返回 EINTR。 |
5.1.5.3、共享内存
--- IPC对象(sytemV),用于传输数据
在内核空间中,申请一段多个进程都可以访问的内存空间,多个进程找到这个空间之后,都可以获取访问。
- 操作步骤:
- ipcs -m --- 查看共享内存
1、创建 / 打开共享内存
功能 | 创建或打开指定 key 共享内存 |
头文件 | #include #include |
函数原型 | int shmget(key_t key, size_t size, int shmflg); |
参数 | key_t key:要创建或打开的共享内存的键值(要打开哪一个)。 |
size_t size:共享内存的大小。 | |
int shmflg:共享内存的功能选项,用 | 连接
| |
返回值 | 成功:返回 共享内存的id(在进程中标识打开的共享内存);失败:返回 -1。 |
功能 | 通过路径与整数来计算一个key值 |
头文件 | #include #include |
函数原型 | key_t ftok(const char *pathname, int proj_id); |
参数 | const char *pathname:计算的共享内存key值的路径。 |
int proj_id:一个整数。 | |
返回值 | 成功:返回key值;失败:返回 -1。 |
2、映射共享内存到进程中
功能 | 映射共享内存的地址到进程中 |
头文件 | #include #include |
函数原型 | void *shmat(int shmid, const void *shmaddr, int shmflg); |
参数 | int shmid:要映射的共享内存id。 |
const void * shmaddr:指针,地址。
| |
int shmflg:映射选择
| |
返回值 | 成功:返回 共享内存的id(在进程中标识打开的共享内存);失败:返回 -1。 |
3、进行通信(往共享内存写、从共享内存读)
*地址 进行操作。
4、解除共享内存映射
功能 | 解除映射的共享内存 |
头文件 | #include #include |
函数原型 | int shmdt(const void * shmaddr); |
参数 | 要解除的共享内存的地址。 |
返回值 | 成功:返回0;失败:返回 -1。 |
5、删除关闭共享内存
功能 | 控制管理共享内存 |
头文件 | #include #include |
函数原型 | int shmctl(int shmid, int cmd, struct shmid_ds * buf); |
参数 | int shmid:要操作的共享内存id。 |
int cmd:操作方式
| |
istruct shmid_ds * buf:属性地址
| |
返回值 | 成功:返回 0 ;失败:返回 -1。 |
5.1.5.4、消息队列
--- IPC对象(sytemV),用于传输数据
消息队列是在内核中的一种特殊通信对象,多个进程都可以访问队列进行通信;消息队列中的传输的消息数据携带了类型。
- 查看消息队列:ipcs -q
- 消息队列操作步骤
1、创建 / 打开消息队列
功能 | 创建 / 打开消息队列。获取消息队列ID |
头文件 | #include #include #include |
函数原型 | int msgget(key_t key, int msgflg); |
参数 | key_t key:要创建 / 打开消息队列的键值。 |
int msgflg:选项,用 | 连接
| |
返回值 | 成功:返回消息队列ID;失败:返回 -1。 |
2、发送 / 接收消息
- 发送消息
功能 | 往消息队列中添加消息 |
头文件 | #include #include #include |
函数原型 | int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); |
参数 | int msqid:要发送消息的消息队列。 |
const void *msgp:要发送消息的地址,把这个地址中的消息发送到消息队列中。
struct msgdata { long type; //必须包含消息类型 data; //消息正文 } | |
size_t msgsz:消息正文长度
| |
int msgflg:选项
| |
返回值 | 成功:返回读取正文大小;失败:返回 -1。 |
- 接收消息
功能 | 从消息队列中读取接收消息。 |
头文件 | #include #include #include |
函数原型 | ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); |
参数 | int msqid:要读取的消息队列ID。 |
void *msgp:读取消息后存储消息的地址,包含类型与正文的首地址。 | |
size_t msgsz:消息正文的大小。
| |
long msgtyp:指定获取哪种类型的消息
| |
int msgflg:选项
| |
返回值 | 成功:返回 0;失败:返回 -1。 |
3、关闭消息队列
功能 | 操作消息队列。 |
头文件 | #include #include #include |
函数原型 | int msgctl(int msgid, int cmd, ... ); |
参数 | int msgid:要操作的哪个消息队列。 |
int cmd:功能选择。
| |
返回值 | 成功:返回 0;失败:返回 -1。 |
5.1.5.5、信号量
--- IPC对象(sytemV),用于通知
信号灯/信号量不是用于传输数据的,而是用于标识一些特性信号的,用来协调各个进程进行工作。
- 特殊的整数值:当整数为0时,就阻塞等待。
通过信号量来控制进程之间执行的顺序。
- 信号量通信操作方式
1、创建信号量
功能 | 创建打开信号量 |
头文件 | #include #include #include |
函数原型 | int semget(key_t key, int nsems, int semflg); |
参数 | key_t key:创建打开信号量的键值(可与共享内存的key值相同)。 |
int nsems:在信号量中,元素个数(信号量的个数)。 | |
int semflg:信号量选项,用 | 连接。
| |
返回值 | 成功:返回 信号量ID;失败:返回 -1。 |
2、初始化信号量的元素值
利用semop()函数中的 SETVAL 参数进行设置。
- 读进程中不需要进行初始化。
1、信号量操作
功能 | 对信号量进行p / v 操作。 |
头文件 | #include #include #include |
函数原型 | int semop(int semid, struct sembuf * sops, size_t nsops); |
参数 | int semid:操作的信号量。 |
struct sembuf * sops:操作信号量结构体地址,在结构体中包含操作方式,可以操作多个元素。
| |
size_t nsops:操作信号量中的哪个元素。 | |
返回值 | 成功:返回 0;失败:返回 -1。 |
- p操作:申请资源
先减1,然后执行。
while(1){
if(xxx > 0){
xxx -= 1;
break;
}
}
- v操作:释放资源
先做,再加1。
…… --- 执行内容
xxx += 1;
2、关闭/删除信号量
功能 | 控制、管理信号量 |
头文件 | #include #include #include |
函数原型 | int semctl(int semid, int semnum, int cmd, ... ); |
参数 | int semid:要操作、管理的哪个信号量。 |
int semnum:要操作管理信号量中的哪个元素。 | |
int cmd:操作命令。
| |
返回值 | 成功:返回 0;失败:返回 -1。 |
5.2、线程
由于进程的资源都是独立的,每个进程都有自己的一份资源,相互没有影响。
- 线程:在一个进程中的多个任务,多个任务可以共享进程中的共享资源;在进程中彼此之间共享一套资源的任务叫做线程。
- 线程和进程一样,也是独立参有系统调度。
- 线程也叫做轻量级进程。
5.2.1、创建线程
功能 | 在进程中创建一个新线程。 |
头文件 | #include |
函数原型 | int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); |
参数 | pthread_t *thread:线程id地址,当创建新线程后,把线程id存储到这个地址。 |
const pthread_attr_t *attr:创建线程时,设置线程的属性。
| |
void *(*start_routine) (void *):函数指针,表示线程执行的任务函数。 | |
void *arg:线程函数的参数。 | |
返回值 | 成功:返回 0 ;失败:返回错误码。 |
备注 |
|
5.2.2、结束线程
线程执行的任务函数结束后,线程结束。
1、调用函数结束
功能 | 结束线程,同时返回 |
头文件 | #include |
函数原型 | void pthread_exit(viod * retval); |
参数 | viod * retval:结束状态,把地址作为结束状态;不需要则填NULL。 |
2、等待线程结束
功能 | 等待指定线程结束,回收线程资源,获取结束状态。 |
头文件 | #include |
函数原型 | void pthread_join(pthread_t thread, viod ** retval); |
参数 | pthread_t thread:指定线程id,等待哪个线程结束。 |
viod ** retval:线程结束状态存储的地址,把结束状态的一级指针存储到哪个地址。 | |
返回值 | 成功:返回0;失败:返回错误码。 |
5.2.3、取消线程
功能 | 取消一个线程 |
头文件 | #include |
函数原型 | int pthread_cancel(pthread_t thread); |
参数 | pthread_t thread:指定线程id,要取消的线程。 |
返回值 | 成功:返回0;失败:返回错误码。 |
5.2.4、线程的同步与互斥
同步
功能 | 创建一个信号量 |
头文件 | #include |
函数原型 | int sem_init(sem_t * sem, int pshared, unsigned int value); |
参数 | sem_t * sem:信号量变量。初始化的信号量。 |
int pshared:在哪里使用。
| |
unsigned int value:要初始化信号量的值。 | |
返回值 | 成功:返回0;失败:-1。 |
- 操作信号量
1、v操作 --- 加1
功能 | 操作信号量 |
头文件 | #include |
函数原型 | int sem_post |
参数 | sem_t * sem:信号量变量。初始化的信号量。 |
int pshared:在哪里使用。
| |
unsigned int value:要初始化信号量的值。 | |
返回值 | 成功:返回0;失败:-1。 |
2、 p操作 --- 减1
功能 | 操作信号量 |
头文件 | #include |
函数原型 | int sem_wait |
参数 | sem_t * sem:信号量变量。初始化的信号量。 |
int pshared:在哪里使用。
| |
unsigned int value:要初始化信号量的值。 | |
返回值 | 成功:返回0;失败:-1。 |
互斥
存在多个线程(进程)可能同时访问资源,造成资源竞争的问题 ---- 竞态。
- 互斥:多个线程或进程在同一时间只能有一个线程或进程进行访问资源,其他线程访问就需等待。
- 互斥锁:每个线程在访问资源时,都加锁才能访问;加锁失败则等待。访问完资源后,进行解锁,通过互斥锁就达到资源的有序访问。
1、互斥锁初始化
功能 | 互斥锁初始化 |
头文件 | #include |
函数原型 | int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); |
参数 | pthread_mutex_t *restrict mutex:要初始化哪个互斥锁变量;变量类型为pthread_mutex_t。 |
返回值 | 成功:返回0;失败:-1。 |
- 加锁
功能 | 查看互斥锁变量是否已经使用,如果已经被使用则等待,没有被使用则使用。 |
头文件 | #include |
函数原型 | pthread_mutex_lock(pthread_mutex_t *restrict mutex); |
参数 | pthread_mutex_t *restrict mutex:要加哪个互斥锁变量;变量类型为pthread_mutex_t。 |
返回值 | 成功:返回0;失败:错误编码。 |
- 解锁
功能 | 解锁已使用的互斥变量。 |
头文件 | #include |
函数原型 | pthread_mutex_unlock(pthread_mutex_t *restrict mutex); |
参数 | pthread_mutex_t *restrict mutex:要解哪个互斥锁变量;变量类型为pthread_mutex_t。 |
返回值 | 成功:返回0;失败:错误编码。 |
总结
在计算机科学中,文件IO(Input/Output)和进程是两个基本概念和重要主题。文件IO涉及读取和写入数据到文件,而进程则涉及并发执行的程序实例。在本次学习中,我了解了文件IO和进程的概念、原理、以及如何在不同编程语言中实现它们。
一、文件IO
文件是存储在计算机上的数据集合,文件IO是操作和管理这些文件的方式。文件IO可以分为输入和输出,输入指的是从文件读取数据,而输出则是将数据写入文件。以下是文件IO的一些关键概念和学习收获:
1. 文件读取和写入:通过打开文件,我们可以使用特定的操作(如读取或写入)来处理文件中的数据。读取文件时,我们可以逐行或以块的形式读取文件内容。写入文件时,我们可以将数据写入文件中,创建新文件或覆盖现有文件。
2. 文件指针:文件指针是一个指向文件中特定位置的指针。在读取或写入文件时,文件指针用于跟踪文件的当前位置。我们可以移动文件指针以读取或写入文件的特定部分。
3. 文件模式和权限:文件模式定义了对文件的访问模式,如读取、写入、追加等。文件权限是指对文件的访问权限,如读取、写入、执行等。学习中我了解了不同操作系统中的文件模式和权限设置方法。
4. 异常处理:在文件IO过程中,可能出现各种异常情况,如文件不存在、权限错误等。学习中我学会了如何处理这些异常并提供适当的错误处理机制。
二、进程
进程是计算机中正在执行的程序实例,它是计算机进行任务处理和资源分配的基本单位。以下是进程的一些关键概念和学习收获:
1. 进程创建和管理:学习中我了解了进程的创建和管理方法,包括创建子进程、进程间通信、进程调度等。例如,在Linux系统中,可以使用fork()函数创建子进程,并使用exec()函数在子进程中执行新的程序。
2. 进程间通信(IPC):多个进程之间可以通过IPC实现数据交换和共享资源。学习中我了解了不同的IPC机制,如管道、消息队列、共享内存等。
3. 进程调度:学习中我了解了进程调度算法和优先级,在多道程序环境中,操作系统如何分配CPU时间给不同的进程。
4. 并发与同步:多个进程同时执行时可能会引发并发和同步的问题。学习中我了解了并发和同步的概念,并学会了使用锁、信号量等机制来控制进程的并发和同步。
总结起来,文件IO和进程是计算机科学中至关重要的概念。通过学习文件IO,我了解了如何读取和写入文件数据,以及如何处理文件异常。通过学习进程,我了解了进程的创建和管理,进程间通信,以及并发和同步的问题。这些知识对于我进一步深入学习和应用计算机科学领域都具有重要意义。