Linux
1.常见指令与权限
1.1指令
1.ls:列出当前目录下的所有文件
-a包括隐藏文件
2.pwd:显示当前所在路径;
3.cd:去指定的路径,作为工作路径;
4.touch:在当前目录下新建一个文件;
5.makdir:在当前位置新建一个目录;
6.rmdir/rm:rmdir与mkdir相对,删除指定目录,rm删除目录或文件
-f只读的文件一样删除;
-r删除所有目录与文件;
-i删除前询问
7.man:查询文档;
**8.cp **复制文件cp [选项] 源文件或目录 目标文件或目录
-r递归复制
9.mv:移动文件 mv [选项] 源文件或目录 目标文件或目录
1.2权限
1.2.1用户权限
Linux下有超级用户和普通用户俩种
切换 su命令;
1.2.2文件权限与文件类型
文件权限:rwx分别为可读可写可执行 二进制表示rwx为111十进制即为7;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kt9Ky4DL-1598114885180)(C:\Users\wanghao\Desktop\面试\知识点\截图\TIM截图20200815230746.png)]
文件权限 : 文件所有者权限 : 文件所有组权限 : 其他用户权限
文件类型:
d:文件夹
-:普通文件
l:软链接(类似Windows的快捷方式)
b:块设备文件(例如硬盘、光驱等)
p:管道文件
c:字符设备文件(例如屏幕等串口设备)
s:套接口文件
文件权限的设置:
chmod 权限 文件名
2.开发工具
2.1软件管理工具yum
yum:是一个Linux下的软件包管理工具,在安装时需要查看网络是否连接使用ping;
安装软件:
yum install 软件
卸载软件
yum remove 软件
2.2编辑器vim
vim:是一款Linux下的编辑器,
vim的使用:
vim / vi 文件名 进入编辑模式
进入默认为正常模式,按a可以调整为插入模式,esc重新回到默认正常模式,正常模式下退出加:输入指令;
wq 保存退出;
w保存;
q!强制退出;
3.编译器gcc/g++
背景知识 :代码转化为可执行程序的过程
**预处理:**进行宏替换,去注释,展开头文件,条件编译;
**编译:**检查语法,将代码转化为汇编代码;
**汇编:**将汇编代码转化为二进制机器码;
链接:将文件链接到一起形成可执行程序;
函数库:分为静态库与动态库,静态库是直接将代码套入,动态库不然,是链接文件到程序;
gcc操作: gcc [选项] 要编译的文件 [选项] [目标文件];
gcc选项:
-o文件输出到目标文件
示例
gcc test.c -o test//将test.c编译为test可执行程序
./test //运行程序
4.项目自动化构建工具make/makefile
make:是针对makefile的一种指令
makefile:编写示例:
main : main.c
gcc main.c -o main
3.进程
3.1操作系统
3.1.1操作系统
-
操作系统存在的意义是为计算机提供一个软硬件交互的工具;
-
操作系统包括 内核(进程管理、内存管理、文件管理、驱动管理) ,其他程序
3.1.2系统调用与库函数
系统调用:操作系统对外的接口,供上层开发使用,这部分系统提供的接口,叫系统调用;
库函数:开发者对系统接口进行封装,形成了库,库的存在有利于二次开发;
3.2进程
基本概念:进程信息放在一个叫进程控制块PCB中,Linux下的PCB是一个task_struct
3.2.1创建进程fork函数
fork函数有俩个返回值,即父子进程,== 0创建子进程,>0为父进程模块,<0创建失败;
父子进程代码共享,数据各自开辟空间,并且私有(采用写时拷贝技术)
3.2.2进程状态
1.R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。
2.S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
3.D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
4.T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
5.X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
6.Z僵尸进程:子进程先于父进程退出,且父进程没有读取到子进程的退出代码,子进程进入僵尸进程;
父进程收不到子进程的退出句柄,则子进程一直处于Z状态;
若子进程一直不退出,pcb一直需要给子进程提供维护,浪费资源;
会造成内存泄漏
避免–进程等待
孤儿进程:父进程先于子进程退出,子进程无人收养,被1号进程init进程收养,子进程被称为孤儿进程
4.进程地址空间
父子进程的地址相同,对应的是物理地址吗?
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量 但地址值是一样的,说明,该地址绝对不是物理地址!
在Linux地址下,这种地址叫做 虚拟地址
我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理
4.1分页存储&&虚拟地址空间
父子进程的内存地址相同但是内容并不一样,是因为使用了页表映射
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8nY3kqTs-1598114885186)(C:\Users\wanghao\Desktop\面试\知识点\截图\TIM截图20200817135428.png)]
**分段式:**地址:段号+段内偏移量 、 段表: 段表+物理起始地址 、将地址空间分为数据段,代码段,方便编译器的地址管理;
分页式:
虚拟地址找到物理地址:
虚拟地址= 页号+页内偏移;
物理地址=块号(由虚拟地址映射可得)*页面大小 +页内偏移量;
段页式:将地址分段,再分页管理;
缺页中断:访问时发现该地址中的数据不存在;
磁盘交换分区:作为交换内存使用,物理内存不够时,按照内存置换算法将物理内存中的某些数据移动到交换分区中保存,腾出内存加载新的数据进行处理;
4.2写时拷贝
写时拷贝:当父子进程不再写入时,代码也是共享的,当一方进行写入时,另一方会存一份副本;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Me5gAjAr-1598114885189)(C:\Users\wanghao\Desktop\面试\知识点\截图\写时拷贝.png)]
不再写入时,代码共享 写入时,各存一份副本
4.3进程退出
正常终止(可以通过 echo $? 查看进程退出码):
- 从main返回
- 调用exit
- _exit
异常退出:
- ctrl + c,信号终止
4.4进程等待
在子进程先于父进程退出,父进程没有接收到子进程退出句柄,会造成僵尸进程,父进程的进程等待可以避免这个问题,避免了内存泄漏;
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息;
4.4.1进程等待的方法:
1.wait方法
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
2.waitpid方法:
pid_ t waitpid(pid_t pid, int *status, int options);
返回值
-
当正常返回的时候waitpid返回收集到的子进程的进程ID;
-
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
-
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
注意
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退 出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
4.5进程替换
进程替换:
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
5.IO
5.1标准库的IO
fopen:打开文件
fclose:关闭文件
fread:读取文件
fwrite:写入文件
r / r+ 、w / w+ a/a+ b:
r只读 w只写;
r+读写 w+读写;
a追加写,a+追加读写;
b按二进制方式读取;
注意事项:文本操作与二进制操作的区别
文本操作是按字符进行读取;
二进制更加精确;
5.2系统调用IO
open、read、write、lseek、close的使用
与c语言不同的是,一个是库函数,一个是系统调用接口
5.3文件描述符
定义:文件描述符是一个非负整数,实质上是内核PCB进程打开文件文件信息描述表的数组下标;
操作:通过文件操作描述符操作文件,就是拿着描述符在内核中找到pcb中的IO信息表的数组表;
重定向:针对描述符的重定向,让描述符执行下一个文件的描述信息;
描述符与文件流指针的关系:文件流指针是FILE结构体,结构体中包含文件描述符成员;
5.4磁盘文件管理
区域:超级块、inode位图、数据块位图、inode表、数据块
文件数据存储:通过文件描述符进入超级块,找到俩个位图,寻找空闲区域进行数据存储;
文件数据获取:通过文件名找到inode节点号,通过节点号找到inode表区域,再找到数据块,取出数据;
软链接与硬链接:
本质:
软链接文件是一个独立的文件,有自己的inode,数据保存的是源文件路径;
硬链接文件与源文件相同是一个文件目录项,共享一个inode;
区别:
- 软连接针对目录可以创建,硬链接不可以;
- 软链接可以跨分区建立,硬链接不可以;
- 删除源文件,软链接文件失效,硬链接可以用;
6.进程间通信
进行间通信的必要性:进程间互相独立,每个进程访问的都是自己的虚拟地址,无法进行通讯;
6.1管道
6.1.1 本质
管道的本质是内核中的缓冲区
6.1.2管道的种类
匿名管道:
1.缓冲区没有标识符,只能通过子进程复制父进程的方式取到管道的操作句柄;
2.只能用于具有亲缘关系的进程之间的通讯;
命名管道:
1.缓冲区具有标识符,可见于文件系统的管道文件,通过打开相同的管道文件访问相同缓冲区;
2.可以用于同一主机的任意进程间通信;
6.1.3管道的特性
- 半双工通信
- 生命周期跟随进程生命周期
- 提供字节流服务 :(字节流是先进先出以字节为单位的连接传输)
- 自带同步互斥;
**同步:**管道没有数据写操作阻塞,数据满了读操作阻塞;
互斥:对临界资源的访问具有唯一性,写入大小不超过PIPE_BUF-4096字节大小保证原子性;
6.1.4代码操作
pipe/mkfifo相关操作
6.2消息队列
6.2.1本质
内核中的一个优先级队列,多个进程通过向同一队列添加/获取节点实现通讯
6.2.2特性
自带同步与互斥
生命周期伴随内核的周期
6.3共享内存
6.3.1本质
一块物理内存,多个进程通过映射同一块物理内存到各自的虚拟地址空间进行访问
6.3.2特性
- 最快的一种进程间通信
- 较其他通讯方式少了用户态与内核态和数据拷贝
- 生命周期随内核
- 需要注意保护,自身不存在同步与互斥
6.3.3代码实现及命令
命令操作:
ipcs -m 查看信息
ipcrm -m 删除信息
6.4信号量
本质:内核中的计数器
生产者消费者模型:
读者写者模板:
7.进程信号
7.1信号概念
信号:信号是一个软件中断,通知进程发生了某个事件,打断进程的当前操作,去处理事件;
信号的种类:
1-31 非可靠信号;
34-64可靠信号;
7.2信号的生命周期
7.2.1信号的产生
硬件:ctrl + c/z/l
软件:kill() / raise() / abort() / alarm() / sigqueue()
7.2.2信号在进程的注册
注册:在进程的pcb中的pending集合中标记信号的到来,以及在sigqueue链表中添加信号节点;
可靠信号的注册:信号若已经注册则不再注册;
非可靠信号的注册:不管信号是否注册,都会注册一个新的信号;
7.2.3信号的处理
处理即执行信号的处理函数
代码操作:signal
7.2.4信号的阻塞
信号来了之后,注册后也不会马上进行处理;
实现:在内核block集合中标记信号,哪些信号被标记,则这些信号暂时不会处理;
代码操作:sigprocmask;
sigempytset/sigfillset/sigaddset/sigismember/sigdelset
8.多线程
8.1线程概念
定义:线程是进程的一个执行流,在Linux下通过PCB实现,同一进程可以有多个线程,这些线程共享进程的资源,因此有时被称为轻量级进程;
Linux下进程与线程的边界:
- 进程是资源分配的基本单位;
- 线程是cpu调度的基本单位;
线程的独有与共享:
- **独有:**栈、寄存器、优先级、信号屏蔽字、标识符;
- 共享:虚拟地址空间、信号处理方式、IO信息、工作路径;
多进程与多线程的多任务处理:
共同点:
多执行流进行多任务并发执行/并发处理的处理效率高;
不同点:
多线程:
- 线程通信更加灵活(包括进程间通信方式外还有全局数据)
- 线程的创建和销毁成本更低;
- 同一进程间的线程调度成本更低;
- 一些系统调用以及异常会对整个进程产生效果;
多进程:
稳定,进程间相互独立,应用于主程序功能稳定运行的场景;
8.2线程控制
8.2.1创建
进行线程控制的接口,都是库函数;
接口解析
int pthread_creat(pthread_t *thread, const pthread_addr_t *arr , void *(*start_routine(void*),void* arg));
参数解析:
- thread:输出型参数,用于获取线程id–线程的操作句柄
- attr:线程属性,用于在创建线程时设置线程属性,通常置空;
- start_routine函数指针,是一个线程的入口函数–线程运行的就是这个函数,函数运行完毕,线程退出;
- arg:通过线程的入口函数,传递给线程的参数;
- 返回值:成功返回0,失败返回错误编号;
tid是一个无符号的长整型数据;
一个线程有唯一的pcb,每一个pcb有一个pid – pid是一个整型数据;
tid与pid的联系:
tid是一个用户态线程的操作句柄,是用户态线程的id,物理层面上实际上是线程这块地址独有的首地址;
pid是一个轻量级进程id,内核中task_struct结构体中的id;
task_struct->pid:轻量级进程id,即ps -elf显示的LWP
task_struct->tgid:线程组id,等于主线程id,即外部的进程id;
8.2.2终止
线程终止:线程入口函数运行完毕,线程会自动退出,在线程入口函数中调用return(但是main函数中的reutrn,退出的是进程而非主线程);
接口介绍:
pthread_exit
void pthread_exit(void* retval);
- 退出线程接口,谁调用谁退出,retval是退出的返回值;
- 主线程退出,并不会导致进程退出,只有所有的线程都退出,进程才会退出;
pthread_cancel
int pthread_cancel(pthread_t thread);
终止一个线程,退出的线程是被动取消的;
8.2.3等待
线程等待:等待一个线程的退出,获取退出线程的返回值,回收线程所占资源;
线程·有一个属性,默认创建出来的这个属性是jionable,处于这个属性的线程,退出后,需要被其他线程等待获取返回值回收资源;
pthread_join:
int pthread_join(pthread_t thread,void **retval);
参数介绍:
thread:需要等待线程tid;
retval:输出型参数,线程的返回值;
线程的返回值是一个void* ,是一个一级指针,若通过一个函数的参数,需要传地址;
默认情况下:一个线程必须被等待,不等待会造成资源泄漏;
8.2.4分离
线程分离:将线程jionable修改为detach属性;
一个线程属性若是jionable那么必须被等待;
一个线程属性若是detach那么这个线程退出后则自动释放资源,不需要被等待(因为资源已经释放)
分离一个线程,一定是不想获取线程的返回值,又不想等待,才会出现分离线程;
int pthread_detach(pthread_t thread);
将线程属性修改为detach
pthread_t pthread_self(void);//返回线程pid
8.3线程安全
8.3.1概念
线程安全:多个执行流同时对临界资源进行争抢访问不会造成数据二义性;
8.3.2互斥
互斥:同一时间只有一个执行流可以访问资源,保证资源访问的安全性;
互斥锁:只有0/1的计数器,用于标记资源的访问状态,在访问临界资源之前先访问互斥锁判断是否允许访问,不允许则使执行流阻塞,允许则资源设置为不可访问,资源访问完毕则改为可访问状态;
示例: A进程和B进程一起争取临界资源BUF,只允许一个进程访问,访问标记为1,释放标记0;
代码操作
1.定义互斥锁变量
pthread_mutex_t
2.初始化互斥锁:
pthread_mutex_init();
3.访问临界资源前加锁:
pthread_mutex_lock();
4.访问临界资源后解锁
pthread_mutex_unlock();
5.销毁互斥锁
pthread_mutex_destroy();
注意事项:
- 互斥锁需在创建执行流之前初始化;
- 在任何有可能退出执行流的地方解锁;
8.3.3死锁
概念:描述执行流阻塞无法推进的状态;
产生:多执行流访问多个有锁资源,但是由于推进顺序不对,导致无法继续;
产生死锁的必要条件
- 互斥条件;(执行流之间存在互斥条件)
- 环路等待条件;(执行流自身没有解锁的情况下,对方在请求该执行流的资源,对方也没有解锁,一直在环路等待)
- 不可剥夺条件;(资源在没有使用完之前,不允许被剥夺)
- 请求与保持条件;(执行流在拥有资源的前提下,可以继续请求已经被占用的资源)
预防:预防其产生的必要条件;
避免:银行家算法
8.3.4同步
同步:通过条件判断,不能获取资源的时候执行流阻塞,保证资源获取的合理性;
条件变量:
- 线程在满足资源的访问条件的时候才可以访问资源,否则就挂起线程,直到满足条件后再唤醒线程;
- 条件变量向外提供了一个使线程等待的接口和唤醒线程的接口+pcb等待队列;
- 条件变量只提供等待和唤醒的接口,什么时候进行操作需要用户自己在进程中判断;
操作代码:
1.定义条件变量:
pthread_cond_t
2.初始化条件变量
pthread_cond_init(pthread_cond_t *,pthread_condattr_t*)
3.使线程挂起休眠的接口:
条件变量需要配合互斥锁一起使用(判断条件本就是一个临界资源)
pthread_cond_wait(pthread_cond_t *,pthread_condattr_t*)//等待被请求资源唤醒
pthread_cond_timedwait(pthread_cond_t *,pthread_mutex_t*,struct timespec);//指定时间唤醒
4.唤醒线程的接口
pthread_cond_signal(pthread_cond_t*)//唤醒至少一个线程
pthread_cond_broadcast(pthread_cond_t*)//唤醒所有线程
5.销毁条件变量
pthread_cond_destory(pthread_cond_t*);
注意事项:
- 在判断时需要使用循环判断;
- 多种不同的角色执行流应该使用多个条件变量;
8.3.5信号量
信号量:
- 可以用于实现进程/线程之间的同步/互斥;
- 信号量的本质是一个计数器+pcb等待队列;
同步:
- 通过自身的计数器对资源进行计数,通过计数器资源的个数,判断进程/线程是否可以达到访问资源的条件;
- 若符合就可以访问,若不符合则调用提供的接口使进程/线程阻塞,其他进程/线程条件满足时,可以唤醒pcb等待队列上面的pcb;
互斥:
保证计数器的值不超过1,就可以保证资源的唯一性,同一时间,进程/线程访问资源,产生互斥;
代码操作:
1.定义信号量:
sem_t
2.初始化信号量
int sem_init(sem_t* sem, int pshared, unsigned int value);
参数解析:
- sem:信号量变量;
- pshared:0/1 、0用于线程间,非0用于进程间;
- value:初始化信号量的初值,初始化计数器上的资源数量;
- 返回值:成功返回0,失败返回-1
3.访问临界资源
访问临界资源之前,访问信号量,如能够访问,计数器-1;
int sem_wait(sem_t* sem);
//通过计数条件判断是否可以访问,不满足则阻塞进程/线程
int sem_trywait(sem_t* sem);
//判断后不满足,则报错返回;
int sem_timedwait(sem_t* sem,const struct timespce* abs_timeout);
//不满足则等待指定时间,超时后返回报错;
4.离开临界资源
离开临界资源,计数器+1,唤醒阻塞进程/线程
int sem_post(sem_t* sem);
//通过信号量唤醒自己阻塞队列上的pcb;
5.销毁信号量
int sem_destory(sem_t* sem);
8.3.6生产者消费者模型:
应用场景:一个进程即需要产生数据,又需要处理数据的场景;
思想:一个执行流产生数据,一个执行流处理数据,执行流之间通过数据缓冲队列进行交互;
优点:解耦合,忙线不均,支持并发;
实现:实现一个线程安全的数据队列,实现两个角色线程;
使用数组实现生产者消费者模型
- 需要满足先进先出的队列特性
- read == write时。表示队列中没有数据;
- 写入数据后,write指针++;
//结构设计
class RingQueue
{
std::vector<int> _queue;
int _capactiy;
int _step_read;
int _step_write;
sem_t_lock;//实现互斥
sem_t_sem_idle;
//对空闲空间进行计数,空间>0才可以写入数据;
sem_t_data;
//对具有数据的空间计数,数据0才可以读取数据;
}
8.3.7读者写者模型:
应用场景:读共享,写互斥;
如多个执行流下的文件读写,可以一起读数据,但是只允许一个执行流写数据;
实现:
读写锁:
- 读者计数器,写者计数器;
- 自旋锁,条件不满足,循环判断;
8.4线程应用
8.4.1线程池
思想:
- 线程的一个池子,有很多的线程,但是不会超过池子的限制;
- 需要用到多执行进行任务处理时,就从线程池中取线程处理;
应用场景:
有大量的数据处理请求,需要执行流并发/并行处理;
风险分析:
若一个数据请求创建一个线程,会产生风险和不必要的消耗
- 线程若不限制数量的创建,在峰值压力下,线程创建过多,资源耗尽,程序有崩溃风险;
- 处理一个任务的时间,创建线程时间T1+任务处理时间T2+线程销毁时间T3 == T,若大量时间浪费在创建与销毁,资源会浪费,效率不高,线程池里面的线程循环任务处理,避免了这些资源损耗;
编写一个线程池:
线程池组成 == 大量线程+任务缓冲队列
传统线程入口函数,处理任务的方式与处理流程过于单一,灵活性不强;
线程池示意图:
线程池中的线程只需要传入方法于需要处理的数据即可,不需要关心如何处理;
typedef void(*_handler)(int data);
class MyTask
{
private:
int data;//需要处理的数据
handler_t handler;//需要的方法
public:
SetTask(int data,handler_t handler);
//用户传入数据,组织出一个任务节点
Run()
{
return _handler(_data);
}
}
class ThreadPool
{
int thr_max;//定义线程的最大数量
std::queue<Mytask> _queque;//任务处理队列
pthraed_mutex_t _mutex;//实现任务队列的操作安全
pthread_cond_t _cond;//实现线程池中的线程同步
}
需要处理什么数据,什么方法,组织一个任务节点,传给线程池,线程池进行处理
8.4.2线程安全的单例模式
单例模式:
一份资源只能被加载一次/单例模式发的方法创建的类只能被实例化一次;
实现:
饿汉模式:
- 资源初始化的时候就去加载,后面可以之间使用,以空间换时间;
- 使用的时候比较流畅,有可能会加载用不上的资源,并且会导致初始化的时间较慢;
- 使用static–将成员变量设置为静态变量,所有对象共用一份资源,程序初始化时就会申请资源,不涉及线程安全;
懒汉模式:
程序初始化比较快,第一次运行某个模块可能比较慢,因为需要加载需要的资源;
使用细节:
- 使用static保证所有对象使用同一份资源;
- 使用volatile,防止过度优化;
- 实现线程安全,保证资源的判断和申请过程是安全;
- 外部二次判断,避免每次成功获取都需要加锁解锁,以及避免死锁;
问题:
STL容器都是线程安全吗 – 不是;见C++11
智能指针是线程安全的吗 ,unipue_ptr 是局部操作 / shared_ptr是原子操作,不涉及线程安全问题;
完结…