Linux问题总结

1.Linux同步机制

1.原子操作
原子操作是由编译器来保证的,保证一个线程对数据的操作不会被其他线程打断。
2.自旋锁

  • 一种非阻塞锁,如果持有锁的线程在短时间内能够释放锁,让其他竞争锁的线程等待一下,无需做用户态内核态之间的转换开销。当持有锁的线程释放后,其他线程能够马上获取到锁,作用主要是为了避免线程用户态和内核态之间的转换开销。

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。

3.读写锁
读锁要实现多个进程能同时读同一数据结构,但是读的过程中不允许写。
写锁要实现仅能有一个进程获取写锁进入临界区,获取写锁时同时保证没有进程已经获取读锁。
4.顺序锁
与读写锁的区别的,赋予了写操作更高的优先级,顺序锁在进程写操作时,可以允许多个线程进行读操作,但不允许进行其他进程再进行写操作。写者可在线程读操作时直接写入。
如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新开始,这样保证了数据的完整性
缺点:
顺序锁的缺陷在于,互斥访问的资源不能是指针,因为写操作有可能导致指针失效,而读操作对失效的指针进行操作将会发生意外错误。
 顺序锁在某些场合比读写锁更加高效,但读写锁可以适用于所有场合,而顺序锁不行,所以顺序锁不能完全替代读写锁。
5.信号量
信号量也是一种锁,和自旋锁不同的是,线程获取不到信号量的时候,不会像自旋锁一样循环区试图获取锁,而是进入睡眠,直至有信号量释放出来时,才会唤醒睡眠的线程,进入临界区执行。
由于使用信号量时,线程会睡眠,所以等待的过程不会占用 CPU 时间。所以信号量适用于等待时间较长的临界区。
信号量消耗 CPU 时间的地方在于使线程睡眠和唤醒线程。
6.互斥量
在这里插入图片描述
7.顺序和屏障
防止编译器优化我们的代码,让我们代码的执行顺序与我们所写的不同,就需要顺序和屏障。

2.Linux常用命令

常用命令>>

1.查看端口是否被其他占用
参考文章>>

1.netstat -anp|grep 进程名字     //查看指定端口号的使用情况
2.netstat -nultp             //查看所有已使用端口的情况

2.查看进程日志

ps -ef|grep *** 查看某个进程 不加grep则表示查看全部进程信息
ps -aux | grep 列出在内存中运行的 全部进程信息
top  动态显示内存中的进程信息
kill -9 pid  强制杀死指定进程
kill -15 PID 正常的方式终止一个进程
ps -ajx

3.查看cpu使用情况

top

4.GDB调试

5.查询可执行文件需要需要哪些依赖包
参考文章>>

yum deplist 软件名
rpm -qR 软件名

6.与指定端口通信

telnet ip port
nc ip port

7.查看主机TCP目前状态

tdx96@ubuntu:~$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED 2
SYN_SENT 1

8.top命令的load average的含义:
参考文章>>
9.安装文件命令
参考文章>>
10.查看一个进程开了多少线程

ps -Lf|  进程号

11.压缩和解压缩命令
参考文章>>

压缩:tar -zcvf a.tar.gz a
解压缩:tar -zxf a.tar.gz

12.查找某个文件

搜索home目录下所有以.txt为后缀名的文件:
[root@zcwyou ~]# find /home -name *.txt*


如果是系统目录,则需要root用户权限,加上sudo是一种获取权限的方法。
查找Linux文件系统上以m4a为后缀的音频文件。
[root@zcwyou ~]# sudo find / -name *.m4a*

在指定目录/home/zcwyou下查找名为url.txt的文件
[root@zcwyou ~]# find /home/zcwyou -name url.txt

13.head和tail命令
参考文章>>
参考文章>>
head命令以行为单位,取文件的内容,后面不接参数时默认打印前10行。
tail命令 – 查看文件尾部内容,默认显示文件file的最后10行:

从第3000行开始,显示1000行。即显示3000~3999行 
cat filename | tail -n +3000 | head -n 1000
 
显示1000行到3000行 
cat filename| head -n 3000 | tail -n +1000
*注意两种方法的顺序 
tail -n 1000:显示最后1000行 
tail -n +1000:从1000行开始显示,显示1000行以后的 
head -n 1000:显示前面1000行
head -n -1000输出文件除了最后1000行的全部内容
 
用sed命令 sed -n '5,10p' filename 这样你就可以只查看文件的第5行到第10行。

14.chmod 修改可执行权限
参考文章>>

r:读取权限,数字代号为4;
w:写入权限,数字代号为2;
x:执行或切换权限,数字代号为1-:不具任何权限,数字代号为0;
s:当文件被执行时,根据who参数指定的用户类型设置文件的setuid或者setgid权限。

文件及目录的权限范围,包括:

u:User,即文件或目录的拥有者;
g:Group,即文件或目录的所属群组;
o:Other,除了文件或目录拥有者或所属群组之外,其他用户皆属于这个范围;
a:All,即全部的用户,包含拥有者、所属群组以及其他用户。

15.修改用户组
参考文章>>
修改所属用户
chown www lifang lifang表示要修改的文件夹名称,www表示要修改的所属用户
修改用户组
chgrp www lifang lifang表示要修改的文件夹名称 www表示要修改的用户组

16 vim命令
参考文章>>
1.删除一行
光标移动到需要删除的行,按esc,键盘按两次d
2.删除指定范围的行
将光标移动到需要删除的行
按一下ESC键,确保退出编辑模式
在dd命令前面加上要删除的行数。例如,如果要删除第4行以下的3行,请按下 3 dd
3.删除多行
比如从第3行到第5行,按ESC,然后输入下面的命令,然后回车。

:3,5d
``
4.删除包含text关键字的行
```cpp
:g/text/d

5.删除全部
按一下ESC键,确保退出编辑模式
按一下:冒号键,(shift + ;)就可以输入:冒号了。
然后输入1,$d
6.复制粘贴
参考文章>>

3.Linux的proc文件夹

参考文章>>
Linux系统上的/proc目录是一种文件系统,即proc文件系统。与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。

基于/proc文件系统如上所述的特殊性,其内的文件也常被称作虚拟文件,并具有一些独特的特点。例如,其中有些文件虽然使用查看命令查看时会返回大量信息,但文件本身的大小却会显示为0字节。此外,这些特殊文件中大多数文件的时间及日期属性通常为当前系统时间和日期,这跟它们随时会被刷新(存储于RAM中)有关。

为了查看及使用上的方便,这些文件通常会按照相关性进行分类存储于不同的目录甚至子目录中,如/proc/scsi目录中存储的就是当前系统上所有SCSI设备的相关信息,/proc/N中存储的则是系统当前正在运行的进程的相关信息,其中N为正在运行的进程(可以想象得到,在某进程结束后其相关目录则会消失)。

大多数虚拟文件可以使用文件查看命令如cat、more或者less进行查看,有些文件信息表述的内容可以一目了然,但也有文件的信息却不怎么具有可读性。不过,这些可读性较差的文件在使用一些命令如apm、free、lspci或top查看时却可以有着不错的表现。

4.Git命令

在这里插入图片描述

参考文章>>

1.创建一个版本库

mkdir git_test     //先创建一个空文件夹
git init   		   //初始化仓库
ls -al              //查看是否成功创建了版本库

2.版本的创建

touch code.txt  //首先创建一个文件

git add code.txt             //提交到暂存区
git commit -m "版本名称"      //提交到工作区

3.查看版本记录

git  log

4.回退一个版本或多个版本

git reset --hard HEAD^        //回退到最新版本的上一版本  HEAD^表示当前版本的上一版本 等价于HEAD~1  HEAD~100 表示当前版本的第100版本     这是向后回滚

5.向前回退一个版本或多个版本

git log  //查看版本记录
git reset --hard + 版本号      

如果git log看不到版本号 则

git reflog       //查看操作记录  找到版本号
git reset --hard + 版本号

6.查看工作树的状态

git status

7.git commit 只会提交暂存区的修改,不会提交工作区的修改,没有进行git add之前都是在工作区修改的
8. 撤销工作区的修改

git checkout -- code.txt

9.将暂存区的修改退回到工作区

git reset HEAD code.txt

10.对比文件的不同

git diff HEAD -- code.txt     //工作区中code.txt和HEAD版本中code.txt的不同
git diff HEAD HEAD^ -- code.txt        //对比HEAD和HEAD ^版本中code.txt的不同

11.从工作区和版本库中删除文件

rm code.txt     //从工作区删除
git rm code2.txt       //从版本库中删除

12.查看分支状态

git branch          //查看几个分支  当前工作在哪个分支

13.创建一个分支并切换到该分支上

git checkout -b -dev    //创建dev分支并切换到dev上工作
git branch           //查看分支状态

切换后 工作树状态如图
在这里插入图片描述
14. 分支合并

git merge dev     //将dev分支合并到当前分支上

15.删除分支

git checkout -d dev       //删除dev分支
  1. rebase和merge的区别
    例如 合并master分支上的改动到当前的feature分支
rebase 会将当前分支的改动直接接到目标分支的尾部,会将feature分支的接入到直接移动到master的尾部。
merge 会产生一个commit,将master上的commit 加入到当前feature分支上

git merge

$ git checkout feature
$ git pull origin master  # 相当于git fetch origin master + git merge origin/master feature

git merge命令会在feature分支创建一个新的“合并的提交”(merge commit),现有的分支不会以任何方式改变。
这意味着每次合并上游(upstream)更改时,feature分支将有一个多余的合并提交。如果master分支更新频繁,这可能会导致feature分支历史记录有大量的合并提交记录。

git rebase

$ git checkout feature
$ git pull --rebase origin master # 相当于git fetch origin master + git rebase master

此命令将整个 feature 分支移动到 master 分支的顶端,有效地将所有新提交合并到master 分支中。和git merge不同的是,- git rebase通过为原始分支中的每个提交创建全新的提交来重写历史记录- 。
rebase的主要好处就是历史记录更清晰,没有不必要的合并提交,没有任何分叉。

5.GDB调试指令

参考文章>>

GDB常用指令
1.生成可调试执行文件

gcc main.cpp -g -o main

2.进入gdb调试

gdb main

3.设置断点

gdb  b 6      //在第6行设置断点

4.查看在哪里设置了断点

gdb  info b

5.打印变量值

gdb p  n

6.程序单步运行

gdb n

7.程序全速运行直到结束

gdb c

8.显示变量值

gdb display n

9.进入函数内部

gdb s
  1. attach到一个进程
(gdb) attach <PID>

12.脱离调试进程

gdb detach PID

13.多进程调试
多进程多线程

“set follow-fork-mode child”开启子进程调试
“set follow-fork-mode parent ”开启父进程调试
“set detach-on-fork off”(默认on)可以同时调试父子进程,在调试一个进程时,另一个进程处于挂起状态
“i inferiors”查看父子进程都处于调试状态
父进程退出后,“i inferiors”的进程状态发生变化,此时子进程还处于挂起状态,“inferior 2”切到子进程,pid=0,说明处于子进程
命令“maintenance info program-spaces”打印当前被调试的进程信息
gdb使用“$_exitcode”记录程序退出时的exit code

11.多线程调试

命令“i threads”可以查看所有线程信息
命令“i threads [tid]”可以查看所有线程信息
命令“ thread apply all|tid_list cmd”在多个线程上执行命令
命令“ thread apply 1-2 bt”在多个线程上执行命令: 在线程1 2上执行命令
命令“set scheduler-locking on”(默认off)可以实现只调试一个线程
gdb调试多线程,默认采用all-stop模式,即只要有一个线程暂停执行,其他线程都会暂停。
命令“set non-stop [mode]”查看当前的non-stop模式状态
non-stop模式下,在指定线程设置断点,不会中断其他线程
non-stop模式下,如果想要continue作用于所有线程,可执行“continue -a”
gdb使用“$_exitcode”记录程序退出时的exit code

6.g++创建静态链接库和动态链接库

参考文章>>
一 动态链接库创建
1.将.cpp文件生成动态链接库

g++ say.cpp -fpic -shared -o libsay.so

2.将动态链接库与主程序连接生成可执行文件

g++ main.cpp -L.  -lsay -o main

-L.:表示要连接的库在当前目录中。
-lsay:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称。

3.查看可执行文件中已连接的动态链接库

ldd main

如果报错,说明没有找到该库,需要修改etc文件夹下的d.so.conf文件,添加一行,内容为动态链接库的目录。
出错原因是链接器ld默认的目录是/lib和/usr/lib。

4.执行可执行文件

./main

二 静态链接库创建
1.编译静态链接库

g++ -c say.cpp

2.使用ar命令创建静态库文件(把目标文档归档)

ar cr libsay.a say.o 

使用nm -s 命令来查看.a文件的内容

归档索引:
_Z3sayv in say.o

say.o:
                 U __cxa_atexit
                 U __dso_handle
                 U _GLOBAL_OFFSET_TABLE_
0000000000000078 t _GLOBAL__sub_I__Z3sayv
0000000000000000 T _Z3sayv
000000000000002f t _Z41__static_initialization_and_destruction_0ii
                 U _ZNSolsEPFRSoS_E
                 U _ZNSt8ios_base4InitC1Ev
                 U _ZNSt8ios_base4InitD1Ev
                 U _ZSt4cout
                 U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
0000000000000000 r _ZStL19piecewise_construct
0000000000000000 b _ZStL8__ioinit
                 U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc

3.连接静态链接库

g++ main.cpp -lsay -L.  -o main 

4.执行可执行文件

./main

7.df du命令的区别

参考文章>>
参考文章>>
1.du :du的英文为:disk usage,含义是磁盘空间使用情况,功能是逐级进入指定目录的每一个子目录并显示该目录占用文件系统数据块的情况,如果没有指定目录,则对当前的目录进行统计。
1)查看当前目录包含子目录的大小

tdx96@ubuntu:~/TDXFolder$ du -sm
1982    .

2)查看当前目录以及子目录的大小

tdx96@ubuntu:~/TDXFolder$ du -h
40K     ./Select
24K     ./selectpollepoll
40K     ./Poll
60K     ./C++Test
20K     ./LIB/protobuf-3.17.3/editors
60K     ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/src/.deps
2.0M    ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/src/.libs
4.0M    ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/src
28K     ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/msvc/2005
48K     ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/msvc/2010
80K     ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/msvc
20K     ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/include/gmock/internal/custom
68K     ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/include/gmock/internal
624K    ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/include/gmock
628K    ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/include
3.3M    ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/lib/.libs
3.3M    ./LIB/protobuf-3.17.3/third_party/googletest/googlemock/lib

2.df
于du不同的是,du是面向文件的命令,只计算被文件占用的空间。不计算文件系统metadata 占用的空间。df则是基于文件系统总体来计算,通过文件系统中未分配空间来确定系统中已经分配空间的大小。df命令可以获取硬盘占用了多少空间,还剩下多少空间,它也可以显示所有文件系统对i节点和磁盘块的使用情况。

df命令作用是列出文件系统的整体磁盘空间使用情况。可以用来查看磁盘已被使用多少空间和还剩余多少空间。
df命令显示系统中包含每个文件名参数的磁盘使用情况,如果没有文件名参数,则显示所有当前已挂载文件系统的磁盘空间使用情况.

  1. df /home

tdx96@ubuntu:~/TDXFolder$ df /home   #指定一个文件夹,查看该文件夹所在磁盘的使用情况
文件系统          1K-块     已用     可用 已用% 挂载点
/dev/sda1      30830500 17699248 11542108   61% /

2)df -a 查看所有文件系统

tdx96@ubuntu:~/TDXFolder$ df -a
文件系统          1K-块     已用     可用 已用% 挂载点
sysfs                 0        0        0     - /sys
proc                  0        0        0     - /proc
udev            1975764        0  1975764    0% /dev
devpts                0        0        0     - /dev/pts
tmpfs            400172     2100   398072    1% /run
/dev/sda1      30830500 17699248 11542108   61% /
securityfs            0        0        0     - /sys/kernel/security
tmpfs           2000852        0  2000852    0% /dev/shm
tmpfs              5120        4     5116    1% /run/lock
tmpfs           2000852        0  2000852    0% /sys/fs/cgroup

3) df -h 以易读的方式展现

tdx96@ubuntu:~/TDXFolder$  df -h
文件系统        容量  已用  可用 已用% 挂载点
udev            1.9G     0  1.9G    0% /dev
tmpfs           391M  2.1M  389M    1% /run
/dev/sda1        30G   17G   12G   61% /
tmpfs           2.0G     0  2.0G    0% /dev/shm
tmpfs           5.0M  4.0K  5.0M    1% /run/lock
tmpfs           2.0G     0  2.0G    0% /sys/fs/cgroup
/dev/loop0       56M   56M     0  100% /snap/core18/2128
/dev/loop1       66M   66M     0  100% /snap/gtk-common-themes/1515
/dev/loop2      768K  768K     0  100% /snap/gnome-characters/726
/dev/loop3      100M  100M     0  100% /snap/core/11420
/dev/loop4       62M   62M     0  100% /snap/core20/1026

8.make file

参考文章>>
参考文章>>
我们在linux下进行编程时,通常使用的是gcc编译器,这种情况下我们通常要去手写编译命令,如:gcc a.c b.c -o app。这虽然看上去很简单,但在实际开发中,往往需要编译的文件有很多,甚至还要去链接一些动态库等等,我们不可能每次都去写一长串的命令。为了方便管理,makefile就诞生了,它可以使用一些简单的规则,来帮助我们构建编译命令,十分方便。
编写规则:

目标文件:依赖项
	执行指令

目标文件:需要生成的可执行文件。
依赖项:生成该执行文件需要依赖哪些项,可以理解为,当依赖项比目标文件的版本新时,就需要执行指令。
执行指令:具体的编译指令

示例:
main.cpp

#include <stdio.h>
 #include "math.h"
 int main()
 {
 int sum = 0;
 sum = add(4, 3);
 printf("sum:%d", sum);
int result = 0;
 result = divide(4, 0);
 printf("result:%d\n", result);
 return 0;
 }

add.cpp

 
#include "math.h"
 
int add(int x, int y)
 {
 return x + y;
 }

divide.cpp

#include <stdio.h>
 
#include "math.h"
 int divide(int x, int y){
 if(y == 0){
 printf("y can't be zero\n");
 return 0;
 }
 return x / y;
 }

minus.cpp

#include "math.h"
int minus(int x, int y){
 return x - y;
 }

multiply.cpp

#include "math.h"
int multiply(int x, int y){
 return x * y;
 }

math.h

int add(int x, int y);
int minus(int x, int y);
int divide(int x, int y);
int multiply(int x, int y);

make file文件编写

calculate:main.o add.o divide.o minus.o multiply.o
        g++ main.o add.o divide.o minus.o multiply.o -o calculate
main.o:main.cpp math.h
        g++ -c main.cpp
add.o:add.cpp math.h
        g++ -c add.cpp
divide.o:divide.cpp math.h
        g++ -c divide.cpp
minus.o:minus.cpp math.h
        g++ -c minus.cpp
multiply.o:multiply.cpp math.h
        g++ -c multiply.cpp
clean:
        rm calculate main.o add.o divide.o multiply.o

几个快捷符号:
参考文章>>

Makefile有三个非常有用的变量。分别是$@,$^,$<代表的意义分别是: 
$@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件。 

9.给文件去重并排序

.linux sort命令实现去重及排序
linux sort命令用于文本文件按行排序

格式:

sort [OPTION]... [FILE]...

参数说明:

-u 去重
-n 数字排序类型
-r 降序
-o 输出文件的路径

使用sort执行去重及排序

sort -uno linux_sort_user_id.txt user_id.txt

查看去重及排序后的文件

wc -l linux_sort_user_id.txt 632042 linux_sort_user_id.txt
head linux_sort_user_id.txt 
0
1
2
3
5
7
8
9
11
12

10.CPU高占用出排查

参考案例>>
排查步骤:
1.使用top命令查看cpu占用率高的进程 ,-p可以以cpu占用率排序
在这里插入图片描述
2.通过top -Hp 2221找到该进程内线程的CPU使用情况
在这里插入图片描述
3.通过jstack 命令定位问题代码
线程PID2244占用的CPU过高
将这个PID转换成16进制 :即2244===》8c4

使用如下指令可以查看当前线程处于一种什么状态,代码在哪里循环

jstack pid |grep tid -A 30(pid:进程id,tid:线程id)
jstack 2221 | grep 8c4 -A 30
 
或者
jstack 2221| grep 8c4  -C5 --color

在这里插入图片描述
线程一般有这么几种状态:

RUNNABLE 线程运行中或I/O等待
BLOCKED 线程在等待monitor锁(synchronized关键字)
TIMED_WAITING 线程在等待唤醒,但设置了时限
WAITING 线程在无限等待唤醒

11.通过PID找到程序所在目录

参考文章>>

1.通过top命令找到某个进程PID
2. ls -al /proc/1782/exe 命令找到该进程在那个目录,属于什么类型

 ls -al /proc/1782/exe 
lrwxrwxrwx. 1 root root 0 831 04:10 /proc/1782/exe -> /usr/local/bin/redis-server

12.死锁排查

参考文章>>

1.查看进程的PID

ps -ef|grep (可执行文件名字)

得到

dyu       6721  5751  0 15:21 pts/3    00:00:00 ./lock

2.使用pstack工具

pstack 6721       打印进程堆栈信息
Thread 5 (Thread 0x41e37940 (LWP 6722)):
#0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000400a9b in func1() ()
#4  0x0000000000400ad7 in thread1(void*) ()
#5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6
Thread 4 (Thread 0x42838940 (LWP 6723)):
#0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000400a17 in func2() ()
#4  0x0000000000400a53 in thread2(void*) ()
#5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6

3.多次调用pstack 查看状态

  • 连续多次查看这个进程的函数调用关系堆栈进行分析:当进程吊死时,多次使用 pstack 查看进程的函数调用堆栈,死锁线程将一直处于等锁的状态,对比多次的函数调用堆栈输出结果,确定哪两个线程(或者几个线程)一直没有变化且一直处于等锁的状态(可能存在两个线程 一直没有变化)。
  1. gdb attach +进程PID
  • gdb attach +进程PID 跟踪进程
 (gdb) info thread
  5 Thread 0x41e37940 (LWP 6722)  0x0000003d1a80d4c4 in __lll_lock_wait ()
  from /lib64/libpthread.so.0
  4 Thread 0x42838940 (LWP 6723)  0x0000003d1a80d4c4 in __lll_lock_wait ()
  from /lib64/libpthread.so.0
  3 Thread 0x43239940 (LWP 6724)  0x0000003d19c9a541 in nanosleep ()
from /lib64/libc.so.6
  2 Thread 0x43c3a940 (LWP 6725)  0x0000003d19c9a541 in nanosleep ()
from /lib64/libc.so.6
* 1 Thread 0x2b984ecabd90 (LWP 6721)  0x0000003d1a807b35 in pthread_join ()
from /lib64/libpthread.so.0
  1. thread +线程PID

查看线程状态

(gdb) thread 5          查看线程
[Switching to thread 5 (Thread 0x41e37940 (LWP 6722))]#0  0x0000003d1a80d4c4 in
__lll_lock_wait () from /lib64/libpthread.so.0
(gdb) where
#0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0
#2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000400a9b in func1 () at lock.cpp:18
#4  0x0000000000400ad7 in thread1 (arg=0x0) at lock.cpp:43
#5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0
#6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6

(gdb) f 3     //
#3  0x0000000000400a9b in func1 () at lock.cpp:18
18          pthread_mutex_lock(&mutex2);       //查看进程在等待哪个锁

6.查看锁被持有的情况

(gdb) p mutex1
$1 = {__data = {__lock = 2, __count = 0, __owner = 6722, __nusers = 1, __kind = 0,
__spins = 0, __list = {__prev = 0x0, __next = 0x0}},
  __size = "\002\000\000\000\000\000\000\000B\032\000\000\001", '\000'
< repeats 26 times>, __align = 2}
(gdb) p mutex3
$2 = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0,
__kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = '\000' < repeats 39 times>, __align = 0}
(gdb) p mutex2
$3 = {__data = {__lock = 2, __count = 0, __owner = 6723, __nusers = 1,
__kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},
  __size = "\002\000\000\000\000\000\000\000C\032\000\000\001", '\000'
< repeats 26 times>, __align = 2}

===》参考文章>>

13.什么是端口

参考文章>>
1.端口是什么?
1.1 是英文port的意译,可认为是设备与外界通讯交流的出口。
1.2 端口可分为虚拟端口和物理端口。
1.2.1 虚拟端口:指计算机内部或交换机路由器内的端口,不可见。例如计算机中的80端口、21端口、23端口等。
1.2.2 物理端口:又称为接口,是可见端口,计算机背板的RJ45网口,交换机路由器集线器等RJ45端口。电话使用RJ11插口也属于物理端口的范畴。

2.我们常说的端口:
2.1 指的是:特指TCP/IP协议中的端口,是逻辑意义上的端口。
2.2 协议端口(protocol port,即我们常说的端口)。
2.3 端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)。
2.4 如果把IP地址比作一间房子 ,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口 可以有65536(即:2^16)个之多!

14.Linux grep egrep和fgrep

参考文章>>

15.浮点数如何存储

参考文章>>

16. Linux框架

C/S 客户端-服务端
MVC :经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。
RPC :RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。RPC 调用分以下两种:

  1. 同步调用
    客户方等待调用执行完成并返回结果。
  2. 异步调用
    客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。
    若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
    异步和同步的区分在于是否等待服务端执行完成并返回结果。

17 Linux 执行shell的几种方式

Linux执行shell的几种方式

18.Shell基本语法

Shell基本语法
一.常见运算法:

数值运算符:
+ :加法
- :减法
* :乘法
/ :除法
% :取余
字符串运算符:
= :字符串相等
!= :字符串不相等
-z :字符串为空
-n :字符串非空
str1 + str2 :拼接字符串
 
数值比较运算符:
-eq:等于 (equal to)
-ne:不等于 (not equal to)
-lt:小于 (less than)
-le:小于等于 (less than or equal to)
-gt:大于 (greater than)
-ge:大于等于 (greater than or equal to)

二.循环语句

while循环

count=0
while [ $count -lt 5 ]
do
    echo "Count: $count"
    count=$((count+1))  # 自增 count 变量
done

for循环

for variable in list
do
    # 代码块
done

for num in {1..5}
do
    echo "Number: $num"
done

for times in $(seq 1 10)
do
        echo "$times"
done

三.判断语句

if 语句

# 单分支语句 ---比较大小
        if (条件表达式);then
                语句1
        fi

# 双分支if 语句
        if (表达式)
                语句1
        else
                语句2
        fi

# 多支条件语句 
        if (表达式)
                语句1
        elif
                语句2
        elif
                语句2
        fi



score=85
if [ $score -ge 90 ]; then
    echo "优秀"
elif [ $score -ge 80 ]; then
    echo "良好"
else
    echo "及格"
fi

case语句

case expression in
    pattern1)
        # 代码块1
        ;;
    pattern2)
        # 代码块2
        ;;
    pattern3)
        # 代码块3
        ;;
    *)
        # 默认代码块
        ;;
esac

fruit="apple"

case $fruit in
    "apple")
        echo "这是一个苹果"
        ;;
    "orange" | "lemon")
        echo "这是一个橘子或柠檬"
        ;;
    *)
        echo "未知水果"
        ;;
esac

四.常见命令
let命令
let 是一种用于执行算术运算的内置命令。它可以用于执行基本的数值计算,并将结果赋给变量

let a=5+3  # 将 5 + 3 的结果赋给变量 a
echo $a   # 输出结果为 8

let "b = 2 * (10 - 4)"  # 将 2 * (10 - 4) 的结果赋给变量 b
echo $b   # 输出结果为 12

c=7
let c++    # 对变量 c 进行自增操作,等价于 c=c+1
echo $c   # 输出结果为 8

let "d = (5 > 3) && (2 < 4)"  # 执行逻辑运算,并将结果赋给变量 d
echo $d   # 输出结果为 1(真)

[[ ]]、[]
单括号 [ ]:
单括号 [ ] 是最基本的条件测试结构,在早期版本的 Bash 中被广泛使用。它可以用于测试多种条件,包括字符串比较、数值比较和文件属性等。
使用单括号时,通常需要在变量周围添加引号,以避免一些特殊字符的问题。并且,需要注意使用不同的比较符号(例如 =、!=、-eq)来进行条件判断。
例如:

if [ "$var" = "abc" -a "$num" -gt 10 ]; then
    echo "条件满足"
else
    echo "条件不满足"
fi

双括号 [[ ]]:
双括号 [[ ]] 是 Bash shell 的扩展条件测试结构,提供了更多的功能和便利性。它支持字符串模式匹配、正则表达式匹配、逻辑操作符的嵌套等。
与单括号不同,双括号内的变量可以不使用引号进行包裹,这意味着在条件测试中可以直接使用变量而不必担心特殊字符的影响。
例如:

if [[ $var == "abc" && $num -gt 10 ]]; then
    echo "条件满足"
else
    echo "条件不满足"
fi

总结一下,单括号 [ ] 是最基本的条件测试结构,适用于基本的条件判断;而双括号 [[ ]] 是扩展的条件测试结构,提供了更多的功能和便利性,可以进行更灵活的条件判断。在使用时,建议优先使用双括号 [[ ]],除非必须与其他 shell 兼容或需要特定的行为时,才使用单括号 [ ]。

()、(())
():
() 是用于创建子shell(subshell)的一种方式,在子shell 中执行相关的命令。子shell 是一个独立的进程,具有自己的环境变量和变量作用域。
使用 () 可以将一组命令括在括号内,并作为一个整体进行处理。这样可以在子shell 中执行这组命令,并在子shell 完成后返回到原来的shell 中。

#!/bin/bash

count=0
(
    count=$((count+1))
    echo "This is in the subshell. Count: $count"
)
echo "Back to the main shell. Count: $count"

以上代码创建了一个子shell,在子shell 中对 count 进行自增操作并输出结果。然后,回到主shell 输出原始的 count 值。

(()):
(()) 是用于执行算术运算和逻辑运算的结构,在其中可以使用变量和运算符进行数值计算和逻辑判断。
(()) 结构会对其中的表达式进行求值,并返回计算结果。它支持基本的算术运算符、逻辑运算符和位运算符。

a=5
b=3
result=$((a + b))
echo "The result is: $result"

以上代码使用 (()) 结构进行算术运算,计算变量 a 和 b 的和,并将结果赋给 result 变量。然后输出计算结果。
需要注意的是,在 (()) 结构中不需要使用美元符号 $ 来引用变量,直接使用变量名即可。
综上所述,() 是用于创建子shell,可以在其中执行一组命令;(()) 是用于执行算术运算和逻辑运算的结构,用于进行数值计算和逻辑判断。

read -p
read -p 是一个用于读取用户输入的命令。-p 参数用于在读取输入之前显示提示信息。

#!/bin/bash
read -p "请输入您的名字: " name
echo "您好,$name!"

上述示例中,使用 read -p 提示用户输入名字,并将用户输入的值存储在 name 变量中。然后通过 echo 命令输出欢迎消息,包含用户输入的名字。
运行脚本时,用户将看到一个提示,等待输入名字。用户输入名字后,脚本将继续执行并输出欢迎消息。

-d
-d 是用作条件测试命令 [ ] 或 test 的选项之一,用于检查给定路径是否为一个存在的目录
以下是一个示例,展示了如何使用 -d 来检查目录是否存在:

path="/data/rivers"

if [ -d "$path" ]; then
    echo "目录存在"
else
    echo "目录不存在"
fi

在上述示例中,如果 /data/rivers 目录存在,则输出 “目录存在”;否则输出 “目录不存在”。
需要注意的是,在条件测试中,path 需要使用引号引起来,以避免路径中存在空格等特殊字符时出现问题。

-a
在条件测试中,使用 -a 可以将多个条件连接起来,并在所有条件都为真时返回真值。-a 实际上是逻辑 AND 运算符的一种替代方式。
但需要注意的是,使用 -a 存在一些问题和不推荐的情况。根据 POSIX 标准,应使用双中括号 [[ ]] 来进行条件测试,且不使用 -a 选项。另外,还可以使用双和号 && 来代替 -a,更加简洁和易读。因此,建议优先使用 [[ ]] 和 && 来执行逻辑 AND 操作,而非使用 -a。
以下是一个示例,展示了 -a 的用法,说明了如何在条件测试中使用 -a 执行逻辑 AND 操作:

if [ "$var1" -gt 10 -a "$var2" == "abc" ]; then
    echo "条件满足"
else
    echo "条件不满足"
fi

在上述示例中,如果变量 var1 大于 10 并且变量 var2 的值等于 “abc”,则条件被认为是真,输出 “条件满足”。否则输出 “条件不满足”。

%F %T
通常用于格式化日期和时间的字符串输出。
%F 是用于格式化日期的占位符,它代表完整的日期,包括年份、月份和日期,使用格式为 YYYY-MM-DD。
%T 是用于格式化时间的占位符,它代表完整的时间,包括小时、分钟和秒钟,使用 24 小时制,格式为 HH:MM:SS。
这些占位符通常用于命令如 date 等,用于获取当前日期和时间,并进行格式化输出。
以下是一个示例,演示了如何使用 %F 和 %T 进行日期和时间的格式化输出:

current_date=$(date +'%F')
current_time=$(date +'%T')

echo "Today's date is: $current_date"
echo "Current time is: $current_time"

以上代码会获取当前的日期和时间,并将其格式化为 %F 和 %T 所示的格式进行输出。

set -x
set -x 是一个用于开启调试模式的选项。当执行这个命令后,Shell 会将脚本的每一行命令都显示出来,并在命令执行之前先输出该行命令。
以下是一个示例,展示了如何使用 set -x 开启调试模式:
复制代码

#!/bin/bash

set -x

# 以下是脚本的主要逻辑
echo "开始执行脚本"
for i in {1..5}; do
    echo "Loop $i"
done
echo "脚本执行完毕"

set +x

# 调试模式已关闭,以下的命令不会显示
echo "调试模式已关闭"

在上述示例中,set -x 命令在脚本的开头设置了调试模式。当脚本执行时,每个命令都会被显示出来,以及其执行结果。set +x 命令用于关闭调试模式,在该命令之后的命令将不再显示。
通过开启调试模式,可以更直观地看到脚本执行过程中每一行命令的执行情况,帮助进行调试或排错。

$1 $2 …
$1 和 $2 是命令行参数的占位符,用于引用在脚本执行时传递给它的参数。
$1 代表第一个命令行参数。
$2 代表第二个命令行参数。
在Shell脚本中,当你执行脚本并传递参数时,可以通过这些变量来引用和访问这些参数。
以下是一个示例,演示了如何在Shell脚本中使用 $1 和 $2:
#!/bin/bash

echo “第一个参数: $1”
echo “第二个参数: $2”
假设脚本名为 script.sh,你可以通过以下方式执行脚本并传递两个参数:
./script.sh value1 value2
脚本会分别输出传递的两个参数的值。在此示例中,$1 的值将是 “value1”,$2 的值将是 “value2”。

19. Linux文件系统

Linux 最经典的一句话是:「一切皆文件」,
在Linux中,文件系统是一种用于控制数据在存储设备上如何存储和检索的方法。它组织和管理磁盘上的文件和目录,并定义了文件的存储结构。文件系统负责跟踪文件的位置、大小、权限、创建和修改时间等信息。Linux支持多种文件系统类型,每种类型都有其独特的特性和用途。

文件系统的作用
详解文件系统

  • 组织数据:文件系统提供了一个层次化的目录结构,使得用户和程序能够方便地存取和管理文件。
  • 数据保护:通过文件权限和所有权机制,文件系统可以保护数据不被未授权访问。
  • 磁盘空间管理:文件系统负责分配和回收磁盘空间,确保磁盘空间的有效利用。
  • 数据持久性:文件系统确保即使在系统重启后,文件数据也能保持不变。
  • 数据备份与恢复:文件系统提供了备份和恢复数据的机制,有助于数据的安全性和完整性。

Linux 文件系统会为每个文件分配两个数据结构:索引节点(index node)和目录项(directory entry),它们主要用来记录文件的元信息和目录层次结构。

  • 索引节点,也就是 inode,用来记录文件的元信息,比如 inode 编号、文件大小、访问权限、创建时间、修改时间、数据在磁盘的位置等等。索引节点是文件的唯一标识,它们之间一一对应,也同样都会被存储在硬盘中,所以索引节点同样占用磁盘空间。
  • 目录项,也就是 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。多个目录项关联起来,就会形成目录结构,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。
    由于索引节点唯一标识一个文件,而目录项记录着文件的名,所以目录项和索引节点的关系是多对一,也就是说,一个文件可以有多个别字。比如,硬链接的实现就是多个目录项中的索引节点指向同一个文件。

注意,目录也是文件,也是用索引节点唯一标识,和普通文件不同的是,普通文件在磁盘里面保存的是文件数据,而目录文件在磁盘里面保存子目录或文件。

目录项和目录是一个东西吗?

  • 虽然名字很相近,但是它们不是一个东西,目录是个文件,持久化存储在磁盘,而目录项是内核一个数据结构,缓存在内存。

== 如果查询目录频繁从磁盘读,效率会很低,所以内核会把已经读过的目录用目录项这个数据结构缓存在内存,下次再次读到相同的目录时,只需从内存读就可以,大大提高了文件系统的效率 ==

那文件数据是如何存储在磁盘的呢?

磁盘读写的最小单位是扇区,扇区的大小只有 512B 大小,很明显,如果每次读写都以这么小为单位,那这读写的效率会非常低。

所以,文件系统把多个扇区组成了一个逻辑块,每次读写的最小单位就是逻辑块(数据块),Linux 中的逻辑块大小为 4KB,也就是一次性读写 8 个扇区,这将大大提高了磁盘的读写的效率。

索引节点是存储在硬盘上的数据,那么为了加速文件的访问,通常会把索引节点加载到内存中。
另外,磁盘进行格式化的时候,会被分成三个存储区域,分别是超级块、索引节点区和数据块区

超级块,用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等。
索引节点区:用来存储索引节点;
数据块区,用来存储文件或目录数据;

我们不可能把超级块和索引节点区全部加载到内存,这样内存肯定撑不住,所以只有当需要使用的时候,才将其加载进内存,它们加载进内存的时机是不同的:

超级块:当文件系统挂载时进入内存;
索引节点区:当文件被访问时进入内存;

虚拟文件系统

文件系统的种类众多,而操作系统希望对用户提供一个统一的接口,于是在用户层与文件系统层引入了中间层,这个中间层就称为虚拟文件系统(Virtual File System,VFS)。

VFS 定义了一组所有文件系统都支持的数据结构和标准接口,这样程序员不需要了解文件系统的工作原理,只需要了解 VFS 提供的统一接口即可。

在 Linux 文件系统中,用户空间、系统调用、虚拟机文件系统、缓存、文件系统以及存储之间的关系如下图:

在这里插入图片描述

Linux 支持的文件系统也不少,根据存储位置的不同,可以把文件系统分为三类

  • 磁盘的文件系统,它是直接把数据存储在磁盘中,比如 Ext 2/3/4、XFS 等都是这类文件系统。
  • 内存的文件系统,这类文件系统的数据不是存储在硬盘的,而是占用内存空间,我们经常用到的 /proc 和 /sys 文件系统都属于这一类,读写这类文件,实际上是读写内核中相关的数据数据。
  • 网络的文件系统,用来访问其他计算机主机数据的文件系统,比如 NFS、SMB 等等。
    文件系统首先要先挂载到某个目录才可以正常使用,比如 Linux 系统在启动时,会把文件系统挂载到根目录。

打开的文件表

系统会跟踪进程打开的所有文件,就是操作系统为每个进程维护一个打开文件表,文件表里的每一项代表「文件描述符」,所以说文件描述符是打开文件的标识。

操作系统在打开文件表中维护着打开文件的状态和信息:

  • 文件指针:系统跟踪上次读写位置作为当前文件位置指针,这种指针对打开文件的某个进程来说是唯一的;
  • 文件打开计数器:文件关闭时,操作系统必须重用其打开文件表条目,否则表内空间不够用。因为多个进程可能打开同一个文件,所以系统在删除打开文件条目之前,必须等待最后一个进程关闭文件,该计数器跟踪打开和关闭的数量,当该计数为 0 时,系统关闭文件,删除该条目;
  • 文件磁盘位置:绝大多数文件操作都要求系统修改文件数据,该信息保存在内存中,以免每个操作都从磁盘中读取;
  • 访问权限:每个进程打开文件都需要有一个访问模式(创建、只读、读写、添加等),该信息保存在进程的打开文件表中,以便操作系统能允许或拒绝之后的 I/O 请求;

读写文件
在用户视角里,文件就是一个持久化的数据结构,但操作系统并不会关心你想存在磁盘上的任何的数据结构,操作系统的视角是如何把文件数据和磁盘块对应起来。

所以,用户和操作系统对文件的读写操作是有差异的,用户习惯以字节的方式读写文件,而操作系统则是以数据块来读写文件,那屏蔽掉这种差异的工作就是文件系统了。

我们来分别看一下,读文件和写文件的过程:

  • 当用户进程从文件读取 1 个字节大小的数据时,文件系统则需要获取字节所在的数据块,再返回数据块对应的用户进程所需的数据部分。
  • 当用户进程把 1 个字节大小的数据写进文件时,文件系统则找到需要写入数据的数据块的位置,然后修改数据块中对应的部分,最后再把数据块写回磁盘。
    所以说,文件系统的基本操作单位是数据块。

20.系统调用

为了保护设备,操作系统不可能让所有的程序都能轻松地访问到任何的文件,因此进程在系统上的运行分为2个级别:

  • (1) 用户态(user mode):用户态运行的进程可以直接读取用户程序的数据;
  • (2) 系统态(kernel mode):系统态运行的程序可以访问计算机的任何资源,不受限制;诸如一些修改寄存器内容的命令,比如次磁盘的IO操作、访问物理页内存、访问网络上的数据包

平常我们的进程几乎都是用户态,读取用户数据,当涉及到系统级别资源的操作(例如文件管理、进程控制、内存管理等)的时候,就要用到系统调用了。

系统调用是计算机操作系统中的一个重要概念,它是用户进程(也就是应用程序)与内核交互的接口。系统调用是由操作系统内核提供的一组函数,它们允许用户进程请求操作系统内核提供的服务和资源,例如文件操作、网络通信、进程控制等。

系统调用的作用是实现操作系统的功能,它可以使用户进程在没有特权级的情况下,通过调用系统提供的函数来访问操作系统提供的功能和资源。系统调用可以看做是用户进程与操作系统内核之间的一种合作方式,它可以保证系统的稳定性、安全性和可靠性。

在计算机操作系统中,系统调用的实现通常包括以下步骤:

  • 用户进程通过系统调用提供的接口向操作系统内核发出请求。

  • 操作系统内核接收到请求后,执行相应的操作,并将结果返回给用户进程。

  • 用户进程根据返回结果进行相应的操作或处理。

系统调用可以分为多种类型,常见的包括文件系统调用、进程控制调用、网络通信调用等。不同的系统调用提供了不同的服务和资源,例如读取文件、写入文件、创建进程、关闭进程、建立网络连接等等。

21.中断

中断是指计算机在执行程序的过程中,出现某些事件需要立即处理时,CPU暂时中止正在执行的程序,转去执行对某种请求的处理程序。当处理程序执行完毕后,CPU再回到先前被暂时中止的程序继续执行。

中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

简单来说就是CPU停下当前的工作任务,去处理其他事情,处理完后回来继续执行刚才的任务,这一过程便是中断。

中断通常分为外部中断和内部中断:
一 .外部中断:

  • 1、可屏蔽中断:通过INTR线向CPU请求的中断,主要来自外部设备如硬盘,打印机,网卡等。此类中断并不会影响系统运行,可随时处理,甚至不处理,所以名为可屏蔽中断。
  • 2、不可屏蔽中断:通过NMI线向CPU请求的中断,如电源掉电,硬件线路故障等。这里不可屏蔽的意思不是不可以屏蔽,不建议屏蔽,而是问题太大,屏蔽不了,不能屏蔽的意思。
    注:INTR和NMI都是CPU的引脚

二.内部中断(软中断,异常)

  • 1、陷阱:是一种有意的,预先安排的异常事件,一般是在编写程序时故意设下的陷阱指令,而后执行到陷阱指令后,CPU将会调用特定程序进行相应的处理,处理结束后返回到陷阱指令的下一条指令。如系统调用,程序调试功能等。尽管我们平时写程序时似乎并没有设下陷阱,那是因为平常所用的高级语言对底层的指令进行了太多层的抽象封装,已看不到底层的实现,但其实是存在的。例如printf函数,最底层的实现中会有一条int 0x80指令,这就是一条陷阱指令,使用0x80号中断进行系统调用。

  • 2、故障:故障是在引起故障的指令被执行,但还没有执行结束时,CPU检测到的一类的意外事件。出错时交由故障处理程序处理,如果能处理修正这个错误,就将控制返回到引起故障的指令即CPU重新执这条指令。如果不能处理就报错。
    常见的故障为缺页,当CPU引用的虚拟地址对应的物理页不存在时就会发生故障。缺页异常是能够修正的,有着专门的缺页处理程序,它会将缺失的物理页从磁盘中重新调进主存。而后再次执行引起故障的指令时便能够顺利执行了。
    3、终止:执行指令的过程中发生了致命错误,不可修复,程序无法继续运行,只能终止,通常会是一些硬件的错误。终止处理程序不会将控制返回给原程序,而是直接终止原程序。

22. 用户态和内核态

用户态:应用程序运行时所处的状态

内核态:操作系统内核运行时所处的状态

当应用程序需要访问操作系统提供的资源或执行一些特权操作时,需要切换到内核态,由操作系统内核来完成相应的操作。在内核态下,应用程序无法直接访问系统资源和硬件设备,需要通过操作系统提供的接口来进行操作。

  • 特权指令:只能由操作系统使用、用户程序不能使用的指令。 举例:启动I/O 内存清零 修改程序状态字 设置时钟 允许/禁止终端 停机

  • 非特权指令:用户程序可以使用的指令。 举例:控制转移 算数运算 取数指令 访管指令(使用户程序从用户态陷入内核态)

用户态和内核态的交互

用户态和内核态是操作系统中的两种不同的运行模式,它们之间的交互主要体现在以下几个方面:

系统调用:用户态的应用程序通过系统调用来请求内核提供特定的服务或执行特定的操作。当应用程序需要执行一些只有内核态才能执行的操作时,例如读写磁盘、网络通信等,它会通过系统调用将控制权交给内核,并等待内核完成相应的操作后再返回用户态。

异常和中断处理:当发生异常或中断事件时,处理器会从用户态切换到内核态,将控制权交给内核来处理异常或中断。内核会根据异常或中断的类型执行相应的处理程序,并可能会进行一些必要的操作,例如保存现场、进行错误处理等。处理完毕后,内核会再次将控制权交还给用户态。

内存管理:用户态的应用程序无法直接访问或操作内核的内存空间,它只能通过内核提供的接口来申请或释放内存。当应用程序需要内存时,它会通过系统调用请求内核分配一块内存空间,并将内存的控制权交给应用程序。应用程序在使用完内存后,再通过系统调用将内存释放回内核。

访问受限资源:内核拥有对系统中所有资源的控制权,包括硬件设备、文件系统、网络等。用户态的应用程序无法直接访问或操作这些资源,它必须通过系统调用请求内核来访问受限资源。内核会对用户的请求进行合法性检查,并根据权限控制策略来决定是否允许访问。

总的来说,用户态和内核态的交互主要是通过系统调用来实现的。用户态的应用程序通过系统调用请求内核提供服务或执行操作,内核在接收到请求后进行相应的处理,并将结果返回给用户态。这种交互机制保证了操作系统的安全性和稳定性。

23.内存对齐

类中的成员变量在内存中并不一定是连续的,它是按照编译器的设置,按照内存块来存储的,这个内存块大小的取值,就是内存对齐。
现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐

内存对齐的原因

  • 平台原因
    不是所有的CPU都能访问任意地址上的任意数据的,有些CPU只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 性能原因
    数据结构应该尽可能地在自然边界上对齐,如果为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。CPU处理器把内存当作一块一块去读取,块的大小可以是2、4、8、16字节大小,这个大小称为内存存取粒度。

== 假设当前处理器的内存存取粒度为4,对于一个int变量(大小4字节),分两种情况讨论== :

数据从第0字节开始存放(已内存对齐)
CPU只需要访存一次,就可以把4字节数据读完,然后存入寄存器。

数据从第1字节开始存放(没有内存对齐)
数据不处于自然边界上,CPU需要分两次访存,第一次先访问[0, 3]字节进入寄存器,第二次访问[4, 7]字节进入寄存器,然后剔除第0、5、6、7字节,仅留第1、2、3、4字节数据进入寄存器。对于未内存对齐的数据,显然大大降低了CPU的处理性能。这种未对齐的情况有些CPU甚至直接开摆。

有效对齐值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。

对齐规则:

  • (1) 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
  • (2) 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

C++不允许一个对象的大小为0,不同对象的地址不能具有相同的地址。
这是因为new需要分配不同的内存地址,不能分配内存大小为0的空间,避免除以sizeof(T)引发除零异常。
所以一个没有数据成员的空类,编译器会为其分配1字节的内存空间,即空类的大小为1。
空基类被继承后,如果派生类有自己的数据成员,那么空基类这1个字节不会添加到派生类中。

#pragma pack(n)
不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n), n= 1,2,4,8,16来改变对齐系数。

内存对齐的参考文章

21. Protobuf vs Json

空间效率:与JSON相比,Protobuf使用一种更有效的方式来表示数据。例如,对于整数,JSON通常采用十进制文本表示,而Protobuf则直接以变长编码的方式存储其二进制形式。这意味着Protobuf在存储相同信息时所需的空间更少。

时间效率:因为Protobuf是二进制格式,所以在解析时速度更快。JSON需要将文本解析为数据结构,这个过程相对较慢。而Protobuf则直接读取二进制数据,减少了解析步骤。

预定义的数据结构:在使用Protobuf时,你需要先定义数据的结构(在.proto文件中),这使得数据在序列化和反序列化时具有确定的结构,减少了处理的复杂性。相比之下,JSON是自描述的,每个数据项都需要单独解析。

版本兼容性:Protobuf支持字段的可选和默认值,这意味着即使发送方和接收方使用的是不同版本的数据结构,也能在一定程度上保证数据的兼容性。而JSON则需要更严格的版本控制。

**语言中立性:**虽然JSON起源于JavaScript,但它现在被广泛接受为一种通用的数据交换格式,几乎所有流行的编程语言都有解析和生成JSON的库。Protobuf同样具有语言中立性,提供了多种编程语言的实现。

工具和支持:由于Protobuf由Google开发和维护,因此它在Google的产品和项目中得到了广泛的应用和支持。同时,它也有一个强大的编译器和丰富的API文档。

**社区和生态:**虽然JSON拥有一个庞大的社区和生态系统,但Protobuf也正逐渐发展其自己的用户基础和工具链。特别是在需要高性能数据处理的场景中,Protobuf的优势更为明显。

22.正则表达式

正则表达式的规则

一、基本匹配规则
直接给出字符,就是精确匹配;

用 \d 可以匹配一个数字;

\w可以匹配一个字母或数字;

.可以匹配任意字符;

\s可以匹配一个空格(也包括Tab等空白符);

特殊字符用‘\’转义,例如‘-’,正则是 \-

例如:
'00\d'可以匹配'007',但无法匹配'00A''\d\d\d'可以匹配'010''\w\w\d'可以匹配'py3''p33''py.'可以匹配'pyc''py0''py!'等等;

'py\s\-\s\d'可以匹配'py - 2'

二、长字符匹配

*表示任意个字符(包括0个);
用 +表示至少一个字符;
用 ?表示0个或1个字符;
用{n}表示n个字符;
用{n,m}表示n-m个字符。
例如:\d{3}\s*\-\s*\d{3,8}

\d{3}代表任意三位数字,\s*代表任意个空格,\-代表特殊符号-,\d{38}代表38位任意数字;

综合起来这个正则表达式匹配的是一个以任意个空格加特殊符号“-”隔开的带区号的电话号码。例如:010 - 123456

三、更精确的匹配
要做更精确地匹配,可以用[]表示范围,比如:

[0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;
[0-9a-zA-Z]可以匹配一个数字或字母,等同于\w;
[0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100''0_Z''Py3000'等等;
[a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
A|B可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python';

^表示行的开头,^\d表示必须以数字开头。

$表示行的结束,\d$表示必须以数字结束。

py可以匹配'python',但是加上^py$就变成了整行匹配,就只能匹配'py'了。

特殊字符

$	匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 '\n''\r'。要匹配 $ 字符本身,请使用 \$。
( )	标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \(\)。
*	匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。
+	匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
.	匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。
[	标记一个中括号表达式的开始。要匹配 [,请使用 \[。
?	匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
\	将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n''\n' 匹配换行符。序列 '\\' 匹配 "\",而 '\(' 则匹配 "("。
^	匹配输入字符串的开始位置,除非在方括号表达式中使用,当该符号在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合。要匹配 ^ 字符本身,请使用 \^。
{	标记限定符表达式的开始。要匹配 {,请使用 \{|	指明两项之间的一个选择。要匹配 |,请使用 \|

普通字符

[ABC]
匹配 […] 中的所有字符,例如 [aeiou] 匹配字符串 “google runoob taobao” 中所有的 e o u a 字母。

[^ABC]
匹配除了 […] 中字符的所有字符,例如 [^aeiou] 匹配字符串 “google runoob taobao” 中除了 e o u a 字母的所有字符。

[A-Z]
[A-Z] 表示一个区间,匹配所有大写字母,[a-z] 表示所有小写字母。

.
匹配除换行符(\n、\r)之外的任何单个字符,相等于 [^\n\r]。

[\s\S]
匹配所有。\s 是匹配所有空白符,包括换行,\S 非空白符,不包括换行。

\w
匹配字母、数字、下划线。等价于 [A-Za-z0-9_]

\d
匹配任意一个阿拉伯数字(0 到 9)。等价于 [0-9]

限定符

* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于 {0,}。 尝试一下 »
+ 匹配前面的子表达式一次或多次。例如,zo+ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。 尝试一下 »
? 匹配前面的子表达式零次或一次。例如,do(es)? 可以匹配 “do” 、 “does”、 “doxy” 中的 “do” 和 “does”。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,o{2} 不能匹配 “Bob” 中的 o,但是能匹配 “food” 中的两个 o。 尝试一下 »
{n,} n 是一个非负整数。至少匹配n 次。例如,o{2,} 不能匹配 “Bob” 中的 o,但能匹配 “foooood” 中的所有 o。o{1,} 等价于 o+。o{0,} 则等价于 o*。 尝试一下 »
{n,m} m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次且最多匹配 m 次。例如,o{1,3} 将匹配 “fooooood” 中的前三个 o。o{0,1} 等价于 o?。请注意在逗号和两个数之间不能有空格。

定位符

^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。
$ 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。
\b 匹配一个单词边界,即字与空格间的位置。
\B 非单词边界匹配。

23.Linux生成coredump文件

  • 命令
gdb attach pid

gcore test.core
即可在当前目录生成core dump文件test.core
  • 被动生成
设置coredump的大小限制

最大限制为unlimited:表示打开生成core dump文件的开关
ulimit -c unlimited
最大限制为0:表示关闭生成core dump文件的开关
ulimit -c 0

表示生成的core dump  文件名格式为"程序名-进程号-时间戳"
echo  -e "%e-%p-%t" > /proc/sys/kernel/core_pattern

24.CmakeLists

用法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Michael.Scofield

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值