一、文件IO
1、一切皆文件
在linux系统中,内核把一切内容都以文件的形式来表示,“一切皆文件”。所有的设备,所有的内容使用linux创建的虚拟文件系统vfs进行管理,将所有的设备用文件的概念封装起来
在应用中使用统一的接口,实现对底层不同文件类的调用,通过vfs中的file_opeartion结构体,提供了一个统一的函数集合,对不同类型文件的支持(某一种文件或设备能够实现的操作都是这个结构体的子集)
内核实现了不同类型文件的区别细节,通过内核提供统一接口操作
系统调用:由内核提供的操作系统的接口函数功能,与内核相关,不同系统可能系统调用不同
应用程序不能直接访问内核,操作系统通过系统调用接口,好让应用程序通过它们使用内核提供的各种服务 系统调用是以稳定、健壮为主,系统调用的功能比较单一,但是在应用使用时需要更加丰富的功能,因此开发了很多库,比如标准C库,在C库中很多功能都是对系统调的封装 |
库函数调用:由标准委员会进行设计,只要使用对应的语言就可以使用的函数调用
两种文件操作方式:
既可以使用操作系统直接提供的系统调用接口,也可以使用标准C库提供的库函数调用
在系统调用和C库中,都有一组函数专门用于文件操作的:比如打开文件、关闭文件、读写文件。如果是系统调用---文件IO(系统IO);如果是库函数调用---标准IO
2、标准IO
标准IO是c库的一部分,实际上是对文件IO的封装,提供了更加丰富的操作方式
1、文件打开
功能 | 打开文件,获取文件指针(来操作表示一个打开的文件) |
头文件 | #include<stdio.h> |
函数原型 | FILE *fopen(const char *pathname,const char *mode); |
参数 | const char *pathname:指针,字符串的首地址,即将打开的文件名(路径)的文件,字符串表示文件 |
char *mode:指针,字符串首地址,字符串表示打开的方式 ″r″:只读打开文本文件,在文件开始位置 ″r+″:读写打开文本文件,在文件开始位置 ″w″:只写方式打开文本文件,如果打开文件存在则清空文件,文件不存在创建文件,在文件开始位置 ″w+″:读写方式打开文本文件,如果打开文件存在则清空文件,文件不存在创建文件,在文件开始位置 ″a″:追加写打开文本文件,如果文件不存在就创建,在文件末尾进行操作(定位到文件末尾) ″a+″:读追加打开文本文件,如果文件不存在就创建,写在文件末尾进行操作 | |
返回值 | 成功:返回文件指针 |
失败:NULL |
返回的文件指针,是一种FILE结构体的地址(指向FILE类型的指针),FILE结构体就是描述打开文件
文件:就是描述这个文件的各种信息内容,能够按照流的方式进行读写
文件指针:描述文件信息地址
2、关闭打开文件
功能 | 关闭打开的文件 |
头文件 | #include<stdio.h> |
函数原型 | Int fclose(FILE *stream) |
参数 | FILE *stream:文件指针,即将关闭的文件 |
返回值 | 成功:返回0 |
失败:返回EOF(-1) |
在程序执行时,默认会为当前程序打开三个文件:
设备 | 文件指针 | 文件描述符 |
标准输入(键盘) | FILE *stdin | 0 |
标准输出(终端显示) | FILE *stdout | 1 |
标准出错输出(终端显示) | FILE *stderr | 2 |
标准IO函数,读写接口非常多:
第一组:每次一个字符的读写标准(fgetc、fputc)
功能 | 获取指定文件中的一个字符 |
函数原型 | #include <stdio.h> |
int fgetc(FILE *stream); | |
int getc(FILE *stream); | |
int getchar(void);==fgetc(stdin) | |
参数 | FILE *stream:文件指针,指定读取字符的文件 |
返回值 | 成功:读取到的字符(以int类型返回) |
失败:EOF,表示本次读取到文件末尾或读取错误失败 | |
备注 | EOF表示读取到文件末尾或者是读取错误失败 |
功能 | 将一个字符写入指定的文件 |
函数原型 | int fputc(int c, FILE *stream); int putc(int c, FILE *stream); int putchar(int c);==fputc(c,stdout) |
参数 | 参数1:字符 int c:要写入的字符 参数2:文件指针 FILE *stream:要写入的文件 |
返回值 | 成功:返回写入的字符值 失败:返回EOF |
第二组:每一行的读写标准IO函数(fgets、fputs)
功能 | 从指定文件中读取最多一行数据 |
函数原型 | char *fgets(char *s, int size, FILE *stream); char *gets(char *s);:从stdin读取,存储到s首地址中 |
参数 | 参数1: char *s:自定义读取数据的存储空间首地址(从文件中读取的数据存储的地址) 参数2: int size:自定义空间的存储空间大小 参数3: FILE *stream:即将读取数据的文件指针(从哪个文件中读取) |
返回值 | 成功:返回存储地址 失败:返回NULL |
备注 | fgets:由于s首地址存在一段空间,读取存储时不能超过这个空间大小,最多读取size-1 size-1 < 一行 ====>size-1 size-1 > 一行 ====>一行 会在读取完的下一个位置添加’\0’ 失败NULL:读取错误失败或当前读取已经在文件末尾读取不到数据 |
功能 | 将数据写入指定文件 |
头文件 | #include<stdio.h> |
数据原型 | int fputs(const char *s, FILE *stream); int puts(const char *s);==fputs(s,stdout); |
参数 | 参数1: const char *s:自定义写入数据,把s首地址的数据写入文件 参数2: FILE *stream:文件指针,要写入的文件 |
返回值 | 成功:返回非负整数 失败:EOF |
备注 | s是首地址,把s首地址开始的每一个字符写入文件,s字符串的字符直到为′\0′ |
第三组:每次读取若干数据块的标准IO函数(读fread、写fwrite)
功能 | 从指定文件中读取若干数据块 |
头文件 | #include<stdio.h> |
函数原型 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); |
参数 | 参数1: void *ptr:指针,读取数据存放的首地址(自定义的存储位置) 参数2: size_t size:整数,要读取每个数据的大小 参数3: size_t nmemb:整数,读取的数据块的个数 参数4: FILE *stream:即将被读取数据的文件的文件指针 |
返回值 | 成功:读取的数据块的个数 失败:返回0 |
备注 | 失败:当前读取已经读取到文件末尾或者读取错误 |
功能 | 从指定文件中写入若干数据块 |
函数原型 | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); |
参数 | 参数1: const void *ptr:指针,要写入的内容数据首地址 参数2: size_t size:整数,要写入每个数据的大小 参数3: size_t nmemb:整数,写入的数据块的个数 参数4: FILE *stream:写入的文件的文件指针 |
返回值 | 失败:返回写入的数据的个数 失败:返回0 |
因为fread、fwrite是严格按照数据块和数据块大小来处理,而不会对数据格式做处理,数据中的特殊字符′\0′,′\n′不会受影响,可以用于二进制文件的操作
判断出错或文件末尾(末尾feof、出错ferror)
功能 | 判断是否到达文件末尾 |
函数原型 | int feof(FILE *stream); |
参数 | 参数1: FILE *stream:判断的文件 |
返回值 | 如果已经到达文件末尾,返回1(真),否则返回0(假) |
功能 | 判断文件操作是否出错 |
函数原型 | int ferror(FILE *stream); |
参数 | 参数1: FILE *stream:判断的文件 |
返回值 | 如果已经到达文件末尾,返回1(真),否则返回0(假) |
标准IO的缓冲机制:
使用标准IO读写操作,在打开文件时会创建缓冲区,用于读写文件
读:从内核中读取一段数据到缓冲区,应用程序读操作就从缓冲区中读取
写:写入文件时,先把数据写入缓冲区,当缓冲区满足一定条件,则会写入文件
三种缓冲区机制:
无缓冲---stderr
行缓冲:只要在缓冲区群遇到′\n′,把缓冲区数据刷新到文件---stdin、stdout
全缓冲:只要缓冲区满,把缓冲区数据刷新到文件----其他用标准IO打开的文件
fflush(文件指针):主动刷新文件缓冲区
3、文件IO
要对文件进行操作,首先要打开你文件,然后进行读写操作
系统IO函数:(与操作系统相关)
1、打开文件(open)
打开一个文件意味着获取到这个文件的访问句柄(文件描述符fd,在内核管理的打开文件顺序表的下标)
功能 | 打开一个指定的文件并获得文件描述符,创建一个新文件 |
头文件 | #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); |
参数 | 参数1: const char *pathname:指针,字符串的地址,字符串是即将打开的文件 参数2: int flags:打开方式 O_RDONLY:读打开 O_WRONLY:写打开 O_RDWR:读写打开---三个是互斥 O_APPEND:以追加方式打开(必须有写) O_CREAT:如果文件不存在,则会创建文件 函数需要第三个参数;mode参数; 不使用,可以没有第三个参数 O_EXCL:与O_CREAT一起使用,如果文件存在,则返回错误信息,用于只创建新文件 O_NOCTTY:如果文件是终端文件,终端就不能作为当前打开文件的程序的控制终端 O_TRUNC:如果文件存在,则清空文件 参数3: mode_t mode:文件创建的权限 权限 == mode & ~umask |
返回值 | 成功:返回打开文件的文件描述符(int) 失败:返回-1 |
flags:打开方式,可以多个合用,如读写、追加、创建-à O_RDWR | O_APPEND | O_CREAT mode:创建权限,以八进制表示,如:0777,0664 |
2、关闭文件(close)
功能 | 通过文件描述符,关闭打开的文件 |
头文件 | #include <unistd.h> |
函数原型 | int close(int fd); |
参数 | 参数1: int fd:文件描述符,即将要关闭的文件 |
返回值 | 成功:返回0; 失败:返回-1 |
3、读写文件(read读、write写)
功能 | 从指定文件中读取数据 |
头文件 | #include<unistd.h> |
函数原型 | ssize_t read(int fd, void *buf, size_t count); |
参数 | 参数1: int fd:文件描述符,指定读取的文件,从fd文件中读取 参数2: void *buf:读取的数据存储的地址,自定义存储位置 参数3: size_t count:想从文件中读取对应的字节数 |
返回值 | 成功:返回实际读取的字节数,0表示当前已经读取到末尾 失败:返回-1 |
功能 | 往指定文件中写入数据 |
头文件 | #include<unistd.h> |
函数原型 | ssize_t write(int fd, const void *buf, size_t count); |
参数 | 参数1: int fd:要写入的文件,往fd文件中写入 参数2: const void *buf:要写入的数据首地址,自定义数据空间,把空间的数据写入 参数3: size_t count:要写入的字节数 |
返回值 | 成功:返回写入的字节数 失败:返回-1 |
4、文件偏移位置(lseek)
在读写文件的时候,是如果保证每次都是从当前的下一个位置操作,有个偏移量的概念,这个偏移位置,可以获取,也可以认为调整
功能 | 调整设置文件位置偏移量 |
头文件 | #include<sys/types.h> #include<unistd.h> |
函数原型 | off_t lseek(int fd, off_t offset, int whenc); |
参数 | 参数1: int fd:要调整位置偏移量的文件描述符 参数2: off_t offset:新的偏移量相对基准点的偏移量 参数3: int whenc:基准点 SEEK_SET :文件开始处 SEEK_CUR:当前位置 SEEK_END:文件末尾处 |
返回值 | 成功:返回新位置距离文件开始处的偏移量 失败:返回-1 |
5、文件属性
在操作文件时,经常需要获取文件的属性,比如类型,权限,大小,所有者等
功能 | 获取文件的元属性 |
头文件 | #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> |
函数原型 | int stat(const char *pathname, struct stat *statbuf); |
参数 | 参数1: const char *pathname:指针,字符串地址,字符串存储文件名 参数2: struct stat *statbuf:结构体指针,当调用函数时,把属性存储到这个地址中,属性结构体 |
返回值 | 成功:返回0 失败:返回-1 |
属性结构体:
struct stat {
dev_t st_dev; //文件所在存储器的设备号
ino_t st_ino; //inode号,每个文件的唯一编号
mode_t st_mode; //文件类型和文件权限
inode 7
文件类型:12-15bit
文件权限:0-8bit
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
};
6、文件目录
如何对目录进行操作
功能 | 打开,存在目录,获取目录指针,可以获取目录项 |
头文件 | #include <sys/types.h> #include <dirent.h> |
函数原型 | DIR *opendir(const char *name); |
参数 | 参数1: const char *name:指针,字符串地址,要打开的目录 |
返回值 | 成功:返回目录指针 失败:返回NULL指针 |
功能 | 在打开的目录,读取项一个目录 |
头文件 | #include <dirent.h> |
函数原型 | struct dirent *readdir(DIR *dirp); |
参数 | 参数1: DIR *dirp:目录指针,读取目录项的目录指针 |
返回值 | 成功:返回目录项结构体(目录中一个文件的基本信息)地址 失败:返回NULL表示读取失败或读取目录 |
struct dirent {
ino_t d_ino; //inode号
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_n me[256]; //文件名
};
4、多文件与库的制作
gcc编译环境:
gcc编译一个程序的流程:
1.预处理
处理以#开头的语句,不进行语法检
a、展开头文件就是把头文件的内容复制到.c文件中
b、处理宏替换#define N 10
-E选项只进行预处理
-o选项输出名字重命名
预处理后的后缀名:.i
2.编译c编译器
将C语言程序转换为汇编程序要进行语法检查
-s将预处理后的文件进行编译
编译后的后缀名:.s表示汇编文件
3.汇编 汇编器
将.s汇编代码 通过汇编器 编译为 机器指令 二进制
-c 将目标文件编译为二进制机器指令
机器指令文件后缀名:.o表示二进制机器指令码文件
4.链接 链接器
将二进制机器码文件(也可能和库文件)进行合并链接形成一个完整的程序文件 ( gcc xx1.o xx2.o xx3.o 程序名)
1、多文件
在工程程序中,通常一个完整的项目不会使用一个文件来编写所有的操作功能,而是按照分类的思想方式,把相关的操作写在一个文件中,通过多个文件组合形成一个完整的工程
分别编写多个文件
vim 文件1.c 文件2.c 文件3.c
在进行多文件编译时,在编译步骤中,只有链接步骤才能合并在一起,之前的步骤都是单个文件独立进行编译,需要对使用的其他文件内容进行声明
通过头文件进行声明,在编译时进行预处理就会把头文件的内容复制到源文件
在编写源文件时,如果用到了其他文件的内容,需要对其他文件的内容进行声明
编译:
gcc文件1.c 文件2.c 文件3.c ……
头文件格式:(.h)
2、库的制作
库:从本质上来说是一种可执行的二进制格式,可以被载入执行
库分为两种:
静态库
静态库,可以直接在编译时就加入到程序中,进行使用
静态库的制作:
//把程序文件编译为二进制文件,只编译不链接
gcc -c xxx.c -o xxx.o
//生成静态库
ar -cr libxxx.a xxx.o
//libxxx.a就是生成的静态库
使用库:
-L:库的路径
-l(小写L):链接库名
-I(大写i):头文件路径
gcc main.c 1.c 2.c +库文件
在编译我们自己的程序时,添加库
gcc xx.c xx2. c ….-L库路径 –lxxx库名 –I头文件
动态库(共享库)
动态库,可以直接在运行时就加入到程序中,进行使用
在编译时,只是将库中的函数符号表和程序进行编译链接
动态库相比于静态库生成的可执行程序体积小,效率,在允许程序时需要依赖动态库
动态库的制作:
gcc -fPIC -shared xxx.c -o libxxx.so
-fPIC:忽略文件位置
-shared:共享库
动态库的使用:
编译:
gcc xx1.c xx2.c +库文件
在编译我们自己的程序时,添加库
gcc xx.c xx2. c ….-L库路径 –lxxx库名 –I头文件 ( -o /.main)
运行:
还需要动态库才能允许
指定动态库,在运行时能够找到动态库
方法1:
将自己的库拷贝到系统的lib下
cp *.so /lib
方法2:
通过指定LD_LIBRARY_PATH临时生效
export LD_LIBRARY_PATH =/home/xxxx绝对路径
方法3:
通过系统的配置文件指定库的路径
配置路径:/etc/lb.so.conf.d 目录下,新加文件或在文件中添加库的路径
使配置生效
sudo ldconfig
二、进程
1、进程的概念
一个程序文件,只是一堆待执行的代码和比分待处理的数据,程序只有加载到内存中,然后让cpu逐条执行代码,才会根据代码内容做出相应的动作,才形成一个活的,动态的进程
进程:正在执行的程序就叫做进程,进程就是程序的一次执行,是程序执行中动态变化的过程,程序:只是这系列动作的指令操作文本
进程是动态的,程序是静态的
进程有执行的概念,而程序没有
进程包含执行时的所有资源,而程序没有
进程执行:
既然进程是一个动态的过程,有诞生的时刻,也有死亡的时刻,每一个进程必然有一个创建它的进程(进程执行是由另一个进程创建),有父进程,init进程系统运行的第一个进程
在进程中,通过pid(进程id)号来标识 每个执行的进程
查看进程的情况:
ps; top
进程的状态:
进程是动态的执行过程,进程在执行时会有很多的运行状态:开始、休眠、等待、暂停、消亡
就绪态:进程在等待队列中排队,占用cpu时间就允许,也可以占用cpu正在运行占用cpu或等待cpu,根据内核的各种调度方式,选择就绪的进程进入cpu执行,执行时间片对应的时间
僵尸态:程序进程已经执行结束,不在执行,资源等待回收,进程结束信息都封存在状态信息中
死亡态:父进程进行回收子进程的资源,查看子进程的结束信息,当父进程已经消亡,子进程被收养,由收养的进程来回收
睡眠态(等待态):处于就绪态,但是缺少某些资源不可操作,会睡眠等待,当有对应资源时就可执行
暂停/停止态:当进程收到暂停信号时,进程的执行就被停止,此时就叫做暂停态,在该状态下不参与调度,但是系统资源不会释放得到执行资源;放在内核的等待队列,当有激活信号(SIGONT),从等待队列移动到就绪队列重新参与调度
2、进程相关操作函数
功能 | 程序创建一个新的进程(执行函数就额外创建一个进程执行),在创建子进程,会拷贝父进程的所有资源到子进程中,用于子进程执行,子进程从fork()的返回值开始执行 |
头文件 | #include <sys/types.h> #include <unistd.h> |
函数原型 | pid_t fork(void); |
返回值 | 父进程:返回子进程的pid 子进程:返回0 失败:返回-1 |
fork函数作用:
创建子进程,把进程内容完全复制一份给子进程执行
fork()成功,分别在父进程和子进程返回值,但值不同,可以根据返回值设置,子进程和父进程执行特定的代码内容
if(pid > 0)
if(pid == 0)
子进程会从fork()执行的下一句
父子进程是相互平等,执行的次序是随机的,父子进程是相互独立的,子进程完全拷贝父进程的内存空间,子进程有自己的内存空间,内存是独立开,互不影响
进程的关闭:
1、主函数return结束进程
2、给进程发送结束信号
kill 发送信号
3、调用进程结束函数
功能 | 结束当前进程,退出本进程 |
头文件 | #include <stdlib.h> #include <unistd.h> |
函数原型 | void exit(int status); void _exit(int status); |
参数 | 参数1: int status:整数&0xff作为当前进程的结束状态 |
备注 | 在进程的任意位置调用都会结束当前进程 结束状态:正常结束:0 异常结束:非0 exit:在结束时会刷新缓冲区 _exit:在结束时不会刷新缓冲区直接结束 |
status参数:在退出 进程时,进程的结束状态,这个值存在于pcb,等于父进程的回收
父进程等待子进程结束回收子进程的状态资源
功能 | 阻塞等待子进程结束,回收子进程资源。获取子进程的结束状态 |
头文件 | #include <sys/types.h> #include <sys/wait.h> |
函数原型 | pid_t wait(int *wstatus); |
参数 | 参数1: int *wstatus:指针,地址,接收子进程状态的地址(获取状态存入地址) |
成功:返回等待回收的子进程的pid |
功能 | 等待指定的一个子进程结束,获取结束状态 |
头文件 | #include <sys/types.h> #include <sys/wait.h> |
函数原型 | pid_t waitpid(pid_t pid, int *wstatus, int op‐tions); |
参数 | 参数1: pid_t pid:子进程pid <-1,表示等待组id的绝对值为pid的进程的任意子进程结束;==-1,表示等待任意子进程结束;==0,表示当前子进程组,任意子进程结束;>0,表示等待pid的子进程结束 参数2: int *wstatus:地址,用于存储状态 参数3: int op‐tions:选项 WNOHANG:非阻塞,查看当前子进程是否结束,结束就回收,没结束不会等待 0:阻塞 |
返回值 | 成功:返回子进程pid 如果是非阻塞且没有子进程结束返回0 失败:返回-1 |
fork创建子进程,子进程执行内容与父进程完全一致,只能在程序中,根据pid值选择执行
改变进程执行内容,替换为其他程序,作为进程执行
功能 | 在进程中,替换进程内容,改为新程序执行 |
头文件 | #include <unistd.h> |
函数原型 | int execl(const char *pathname, const char *arg, ... /* (char *) NULL */); |
参数 | 参数1: const char *pathname:要替换的程序的路径 参数2-n:可变参数,字符串,用于表示程序执行的参数 参数 参数2: const char *arg:程序路径名 参数3: const char *arg:程序名的参数 参数n: NULL:表示参数结束 |
返回值 | 失败:返回-1 |
函数原型 | int execlp(const char *file, const char *arg, ... /* (char *) NULL */); |
参数 | 参数: 不是完整的路径,只需要程序名,execlp调用时会从系统路径($PATH)中查找 |
功能 | 使用新的程序来替换当前进程 |
头文件 | #include <unistd.h> |
函数原型 | int execv(const char *pathname, char *const argv[]); |
参数 | 参数1: const char *pathname:替换的程序路径名 参数2: char *const argv[]:指针数组,数组每个元素为char*类型指针,每个元素用于程序执行的参数列表,执行新程序的所有参数都存放到指针数组中 |
返回值 | 失败:返回-1 |
3、守护进程
1、交互进程:进程的执行有交互过程,如终端进程
2、批处理进程:指定顺序,依次执行多个进程内容,如shell脚本
3、守护进程(精灵进程):随着系统启动而启动执行,系统关闭结束才结束进程
周期性的执行某项任务或者等待某个事件产生的进程,它的运行不依赖任何shell终端,它的生存周期较长,从开机运行到关机结束
ZAII
进程组:
linux进程以组为单位,在进程组的第一个进程进程组组长,管理整个进程组
会话:
管理一个或多个进程组,在会话的第一个进程组,就是会话的组长,关闭整个会话时,会把会话中的所有进程都结束关闭
创建守护进程:
1、创建子进程,退出父进程
pid_t pid = fork();
if(pid>0){
exit(0);
}
2、在子进程中创建新的会话
setsid();
3、改变子进程的工作目录
chdir(“”);
4、修改子进程的文件操作掩码
umask();
5、关闭所有的文件描述符
int s =getdtablesize()//获取最大的打开的文件描述符+1
close(0~s-1)
4、进程间通信
每个进程都有自己运行的所有资源,每个进程都是独立的相互不干扰的,每个进程都是使用自己的资源空间
1、管道
在内核的内存空间设置一个能够传输数据的通道,要进程通信的进程通过文件IO的操作方式,操作这个通道,进行数据传输
要进行通信的进程,一个去创建管道,另外的就连接到管道,进行通信
无名管道:
管道没有名字标识,无法在之后找到管道文件
无名管道只能用于父子进程间通信(因为子进程会复制父进程的所有资源)
创建无名管道:半双工通信,单工通信
功能 | 创建打开无名管道文件,得到管道的文件描述符,操作管道文件 |
头文件 | #include <unistd.h> |
函数原型 | int pipe(int pipefd[2]); |
参数 | 参数1: int pipefd[2]:数组地址,用于存储创建打开的文件描述符 |
返回值 | 成功:返回0 失败:返回-1 |
有名管道:
管道有名字标识,创建有名管道后,在对应的目录中存在对应名字的管道文件
进程打开管道文件后,在内存空间中使用管道文件(管道文件大小永远为0)
对有名管道的使用:就是对文件的操作
操作有名管道:
1、打开有名管道
open();//读或写
2、从管道中读取或往管道中写入,按照文件IO操作
read();、write();
3、关闭管道文件
close()
创建管道文件:
功能 | 创建一个有名管道文件 |
头文件 | #include <sys/types.h> #include <sys/stat.h> |
函数原型 | int mkfifo(const char *pathname, mode_t mode); |
参数 | 参数1: const char *pathname:创建管道文件的名字 参数2: mode_t mode:创建管道文件的读写权限 |
返回值 | 成功:返回0 失败:返回-1 |
2、信号
信号是一种通知,而不是携带数据进行传输,给指定的进程产生一个通知信号
对于linux内核,支持的信号
kill -l
内核中的信号,都有一个设定好的默认操作,当进程接收到信号后可移执行默认操作
19:暂停信号。系统的暂停信号
20:暂停信号。由控制终端发起的暂停信号
17:忽略
18:恢复运行
21-22:暂停; 23/28:忽略
对于进程而言,当进程接收到信号后,可以支持三种操作:
1执行该信号的缺省动作(内核规定的信号操作)
2进程对该信号设置为忽略,丢弃该信号(接收到信号什么都不做):SIGSTOP和SIGKILL是特殊信号不能忽略
3进程捕获信号,进程中接收到该信号后,按照进程的设定操作进行
当向进程发送信号时,进程就会被接收到对应的信号,进行处理
信号操作:
1、发送给信号
功能 | 给指定进程发送信号 |
头文件 | #include <sys/types.h> #include <signal.h> |
函数原型 | int kill(pid_t pid, int sig); |
参数 | 参数1: pid_t pid:发送给哪个pid进程 >0:发送给指定pid进程号的进程 ==0:发送给当前进程同一个进程组的所有进程 -1:发送给能够发送的所有进程 <-1:发送信号给组id == |pid|的进程组 参数2: int sig:发送的信号 |
返回值 | 成功:返回0 失败:返回-1 |
2、进程接收信号的处理
功能 | 设置进程捕获信号之后的处理方式 |
头文件 | #include <signal.h> |
函数原型 | typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); |
参数 | 参数1:信号 int signum:设定哪个信号的处理方式 参数2:信号的处理方式 sighandler_t handler:函数指针,函数地址,当之后捕获到对应的信号后 ,调用哪个函数来处理 SIG_IGN:对信号进行忽略 SIG_DFL:对信号进行默认操作 handler:对信号调用哪个函数处理 |
返回值 | 成功:返回参数2的值 失败:返回SIG_ERR |
3、给当前进程发送信号
功能 | 给当前调用的进程发送指定的信号 |
头文件 | #include <signal.h> |
函数原型 | int raise(int sig); |
参数 | 参数1: int sig:发送的信号 |
返回值 | 成功:返回0 失败:返回非0 |
4、挂起进程等待信号
功能 | 挂起睡眠等待,直到接收到信号 |
头文件 | #include <unistd.h> |
函数原型 | int pause(void); |
返回值 | 失败:返回-1 |
3、共享内存---IPC对象(sytemV)
在内核空间中,申请一段多个进程都可以访问的内存空间,多个进程找到这个空间之后,都可以取访问
操作功能:
1、创建/打开共享内存
int shmget(key_t key, size_tsize, int shmflg);
功能 | 创建或打开指定key的共享内存 |
头文件 | #include <sys/ipc.h> #include <sys/shm.h> |
函数原型 | int shmget(key_t key, size_tsize, int shmflg); |
参数 | 参数1: key_t key:要创建或打开的共享内存的键值 参数2: size_tsize,:共享内存的大小 参数3: int shmflg:共享内存选项 IPC_CREAT:如果key对应的共享内存不存在,就创建 mode:共享内存的访问权限(如:0664) |
返回值 | 成功:返回共享内存id,在进程中表示打开的共享内存 失败:返回0 |
计算Key值:
key_t ftok(const char *pathname, int proj_id);
参数:路径名;项目id,非0整数,只有低8位有效
2、映射共享内存到进程中
oid *shmat(int shmid, const void *shmaddr, int shmflg);
功能 | 映射共享内存的地址到进程 |
头文件 | #include <sys/types.h> #include <sys/shm.h> |
函数原型 | oid *shmat(int shmid, const void *shmaddr, int shmflg); |
参数 | 参数1: int shmid:要映射的共享内存id 参数2: const void *shmaddr:指针,地址 不为UNLL(地址):表示共享内存映射到这个地址(根据shmaddr来映射区域) NULL:系统会自动选择一个地址区域来进行映射 参数3: int shmflg:映射选项 SHM_RDONLY:以只读方式映射共享内存 0:读写 |
返回值 | 成功:返回映射的地址 失败:返回(void*)-1 |
3、进行通信(往共享内存写,从共享内存读)
4、解除共享内存映射
int shmdt(const void* shmaddr);
功能 | 解除已经映射的共享内存 |
头文件 | #include <sys/types.h> #include <sys/shm.h> |
函数原型 | int shmdt(const void* shmaddr); |
参数 | 参数1: const void* shmaddr:要解除的映射的共性内存地址 |
返回值 | 成功:放回0 失败:返回-1 |
5、删除共享内存
int shmctl(int shmid, int cmd,struct shmid_ds *buf);
功能 | 控制管理共享内存 |
头文件 | #include <sys/ipc.h> #include <sys/shm.h> |
函数原型 | int shmctl(int shmid, int cmd,struct shmid_ds *buf); |
参数 | 参数1: int shmid:要操作的共享内存id 参数2: int cmd:操作方式 IPC_STAT:获取共享内存属性 IPC_SET:设置共享内存属性 IPC_RMID:删除共享内存 参数3: struct shmid_ds *buf:属性 地址 设置时,就是把这个地址的属性写入共享内存;获取时,就是把共享内存的属性添加这个地址中 |
4、消息队列---IPC对象
消息队列是在内核中的一种特殊通信对象,多个进程都可以访问队列,进行通信,消息队列中的传输
类型用于标识当前消息可以被谁读取
消息队列的操作:
1、创建/打开消息队列
功能 | 创建打开消息队列,获取消息队列id |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> |
函数原型 | int msgget(key_t key, int msgflg); |
参数 | 参数1: key_t key:创建打开消息队列的键值 参数2: int msgflg:选项 IPC_CREAT:不存在则创建 Mode:打开权限 |
返回值 | 成功:返回消息队列id 失败:返回-1 |
2、发送、接收消息
功能 | 往消息队列中添加消息 |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> |
函数原型 | int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); |
参数 | 参数1: int msqid:要发送的消息队列 参数2: const void *msgp:要发送消息的地址,把这个地址中的消息发送(消息队列) 消息结构体: struct msgdata { long type;//必须包含,消息类型 data;//消息正文 } 参数3: size_t msgsz:消息正文长度 sizeof(struct msgdata )-sizeof(long) 参数4: int msgflg:选项 0:阻塞 IPC_NOWAIT:非阻塞 |
返回值 | 成功:返回 失败:返回-1 |
接收消息:msgrcv()
功能 | 从消息队列读取接收消息 |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> |
函数原型 | ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg); |
参数 | 参数1: int msqid:要读取的消息队列id 参数2: void *msgp:读取消息后存储的地址,包含类型与正文的首地址 参数3: size_t msgsz:正文的大小 参数4: long msgtyp:指定获取哪种类型的消息 >0:获取指定类型消息 ==0:获取消息队列中第一个消息不管类型 参数5: int msgflg:选项 0:阻塞 IPC_NOWAIT:非阻塞 |
返回值 | 成功:返回正文大小 失败:返回-1 |
3、关闭消息队列
功能 | 操作消息队列 |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> |
函数原型 | int msgctl(int msqid, int cmd, struct msqid_ds *buf); |
参数 | 参数1: int msqid:要操作哪个消息队列 参数2: int cmd:功能选择 IPC_STAT:拷贝消息队列属性,参数3的地址中 IPC_SET:把参数3地址中属性,设置到消息队列 IPC_RMID:删除消息队列,参数3:NULL |
5、信号量---IPC对象
信号灯/信号量,不是用于传输数据的,而是用于标识一些特性信号的,用来协调各个进程进行工作
通过信号量,控制进程之间的顺序
p操作:申请资源
先-1,然后执行
while(1)
{
if(xx>0)
{xx-1;break;}
}
v操作:释放资源
先做,然后+1
……
xx+1
信号量通信操作:
1、创建/打开信号量
功能 | 创建/打开信号量 |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
函数原型 | int semget(key_t key, int nsems, int semflg); |
参数 | 参数1: key_t key:创建打开信号量的键值 参数2: int nsems:在信号量中,元素个数(信号量的个数) 参数3: int semflg:信号量选项 IPC_CREAT:没有key信号量,就创建 mode:打开权限,0664 |
返回值 | 成功:返回信号量id 失败:返回-1 |
2、信号量操作
p、v操作
功能 | 对信号量pv操作 |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
函数原型 | int semop(int semid, struct sembuf *sops, size_t nsops); |
参数 | 参数1: int semid:操作的信号量 参数2: struct sembuf *sops:操作信号量结构体地址,在结构体中包含操作方式,可以操作多个元素 sem_num:操作第几个信号灯元素 sem_op:做什么操作 -1:减一 1:加一 0:一直等待信号量为0 sem_flg:是否阻塞 0:阻塞 IPC_NOWAIT:非阻塞 参数3: size_t nsops:操作信号量中的元素 |
返回值 | 成功:返回0 失败:返回-1 |
3、关闭删除信号量
功能 | 控制信号量,管理信号量 |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
函数原型 | int semctl(int semid, int semnum, int cmd, ...); |
参数 | 参数1: int semid:要管理操作的信号量 参数2: int semnum:要操作管理信号量哪个元素 参数3: int cmd:操作命令 IPC_STAT:获取信号量的属性 参数4: 属性结构体的地址,把信号量中获取的存储到地址中 IPC_SET:设置信号量的属性 参数4: 属性结构体的地址,把结构体的属性内容设置到信号中 IPC_RMID:删除信号量,不需要第4个参数 SETVAL:设置信号量中指定元素的值 参数4:整数值 |
三、线程
由于进程的资源都是独立的,每个进程都有自己的一份资源,相互没有影响
线程:在一个进程中的多个任务,这多个任务可以共享进程中的共享资源,在进程中彼此之间共享一套资源的任务叫做线程。线程和进程一样也是独立参与系统调度
线程也叫做轻量级进程
1、创建线程:
功能 | 在进程中创建一个新线程(任务) |
头文件 | #include <pthread.h> |
函数原型 | int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg); |
参数 | 参数1: pthread_t *thread:线程id地址,当创建新线程后,把线程id存储到这个地址 参数2: const pthread_attr_t *attr:创建线程时,设置线程的属性 NULL:设置为默认属性 参数3: void *(*start_routine) (void *):函数指针,线程执行的任务函数 参数4: void *arg:线程函数的参数 |
返回值 | 成功:返回0 失败:返回错误 |
2、线程结束:
1、线程函数结束,则线程结束
2、调用pthread_exit(结束状态)
功能 | 结束线程,同时返回线程的结束状态 |
头文件 | #include <pthread.h> |
函数原型 | void pthread_exit(void *retval); |
参数 | 参数1: void *retval:结束状态,把地址作为结束状态 |
3、等待线程结束pthread_join()
功能 | 线程资源,获取结束状态 |
头文件 | #include<pthread.h> |
函数原型 | int pthread_join(pthread_t thread,void ** retval); |
参数 | 参数1: pthread_t thread: 指定线程id,等待哪个线程结束 参数2: void ** retval:线程结束状态存储的地址,把结束状态一级指针,存储到哪个地址 |
返回值 | 成功:返回0 失败:返回错误码 |
4、取消线程:pthread_cancel
int pthread_cancel(pthread_t thread);
参数:
pthread_t *thread:线程id地址,当创建新线程后,把线程id存储到这个地址
线程的同步互斥:
1同步:
通过信号量进行线程同步
信号量操作:
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能 | 创建信号量 |
头文件 | #include <semaphore.h> |
函数原型 | int sem_init(sem_t *sem, int pshared, unsigned int value); |
参数 | 参数1: sem_t *sem: 信号量变量,初始化的信号量 参数2: int pshared: 在哪里使用 0:在一个进程中线程使用 参数3: unsigned int value:信号量变量要初始化为多少值 |
返回值 | 成功:返回0 失败:返回-1 |
解锁信号量:
#include<semapore.h>
int sem_post(sem_t *sem);//信号量 +1----v操作
int sem_wait(sem_t *sem); //信号量 -1----p操作
2互斥:
多个线程(进程)可能同时访问资源,造成资源竞争-----竞态
多个线程或进程在同一时间只能有一个线程进程进行访问资源,其他线程需要访问就等待
互斥锁:
每个线程在访问资源时,都加锁才能访问,加锁失败等待,访问完资源后,进行解锁,通过互斥锁就可以到达资源的有序访问
安装pthread_mutex_init()手册:
方法:sudo apt install manpages-posix-dev
(1)互斥锁初始化:pthread_mutex_init()
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数1:
互斥锁变量的地址,表示要初始化哪个互斥锁变量
参数2:
属性:一般为NULL,默认属性
(2)加锁: pthread_mutex_lock()
查看互斥锁变量是否已经别使用,如果已经被使用就等待,没有被使用就使用
(3)解锁:pthread_mutex_unlock()