## 本文参考
[markdown教程](https://blog.csdn.net/weixin_38391755/article/details/80380786)
https://www.jianshu.com/p/53c082e8aef2
## linux内核文件下载地址
https://mirrors.edge.kernel.org/pub/linux/kernel/
## gdb调试
### 预备知识
#### 编译
程序的调试过程主要有:单步执行,跳入函数,跳出函数,设置断点,设置观察点,查看变量。
gdb主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数即可。
如果没有-g,将看不见程序的函数名和变量名,代替它们的全是运行时的内存地址。
当用-g把调试信息加入-g 和 -ggdb 都是令 gcc 生成调试信息,但是它们也是有区别的
| 选项 | 描述 |
| ---- | ------------------------------------------------------------ |
| g | 该选项可以利用操作系统的“原生格式(native format)”生成调试信息。GDB 可以直接利用这个信息,其它调试器也可以使用这个调试信息 |
| ggdb | 使 GCC为GDB 生成专用的更为丰富的调试信息,但是,此时就不能用其他的调试器来进行调试了 (如 ddx) |
-g 和 -ggdb 也是分级别的
| 选项 | 描述 |
| ---- | ------------------------------------------------------------ |
| g1 | 级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段 |
| g2 | 这是默认的级别,此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息 |
| g3 | 包含级别2中的所有调试信息,以及源代码中定义的宏 |
#### gdb内部常用命令
| 命令 | 简写 | 描述 |
| :-------------------- | ---- | ------------------------------------------------------------ |
| file [filename] | 无 | 装入想要调试的可执行文件 |
| kill [filename] | k | 终止正在调试的程序 |
| break [file:]function | b | 在(file文件的)function函数中设置一个断点 |
| clear | 无 | 删除一个断点,这个命令需要指定代码行或者函数名作为参数 |
| run [arglist] | r | 运行您的程序 (如果指定了arglist,则将arglist作为参数运行程序) |
| bt | 无 | Backtrace: 显示程序堆栈信息 |
| print expr | p | 打印表达式的值 |
| continue | c | 继续运行您的程序 (在停止之后,比如在一个断点之后) |
| list | l | 列出产生执行文件的源代码的一部分 |
| next | n | 单步执行 (在停止之后); 跳过函数调用 |
| nexti | n | 执行下一行的源代码中的一条汇编指令 |
| set | 无 | 设置变量的值。例如:set nval=54 将把54保存到nval变量中 |
| step | s | 单步执行 (在停止之后); 进入函数调用 |
| stepi | s | 继续执行程序下一行源代码中的汇编指令。如果是函数调用,这个命令将进入函数的内部,单步执行函数中的汇编代码 |
| watch | 无 | 使你能监视一个变量的值而不管它何时被改变 |
| rwatch | | 指定一个变量,如果这个变量被读,则暂停程序运行,在调试器中显示信息,并等待下一个调试命令。参考rwatch和watch命令 |
| awatch | | 指定一个变量,如果这个变量被读或者被写,则暂停程序运行,在调试器中显示信息,并等待下一个调试命令。参考rwatch和watch命令 |
| delete | | delete 断点号 可以删除watch断点 |
| Ctrl-C | | 在当前位置停止执行正在执行的程序,断点在当前行 |
| disable | | 禁止断点功能,这个命令需要禁止的断点在断点列表索引值作为参数 |
| display | | 在断点的停止的地方,显示指定的表达式的值。(显示变量) |
| undisplay | | 删除一个display设置的变量显示。这个命令需要将display list中的索引做参数 |
| enable | | 允许断点功能,这个命令需要允许的断点在断点列表索引值作为参数 |
| finish | | 继续执行,直到当前函数返回 |
| ignore | | 忽略某个断点制定的次数。例:ignore 4 23 忽略断点4的23次运行,在第24次的时候中断 |
| info [name] | i | 查看name信息 |
| load | | 动态载入一个可执行文件到调试器 |
| xbreak | | 在当前函数的退出的点上设置一个断点 |
| whatis | | 显示变量的值和类型 |
| ptype | | 显示变量的类型 |
| return | | 强制从当前函数返回 |
| txbreak | | 在当前函数的退出的点上设置一个临时的断点(只可使用一次) |
| make | | 使你能不退出 gdb 就可以重新产生可执行文件 |
| shell | | 使你能不离开 gdb 就执行 UNIX shell 命令 |
| help [name] | | 显示GDB命令的信息,或者显示如何使用GDB的总体信息 |
| quit | | 退出gdb |
### 例子
#### 要调试的程序 hello.c
```c
1 #include<stdio.h>
2 void fun1(){
3 printf("hello\n");
4 printf("world\n");
5 printf("hhhhhhhh\n");
6 }
7 int main(int argc,char **argv){
8 int aa = 199;
9 printf("%d\n",aa);
10 fun1();
11 aa = 100;
12 }
```
#### 编译程序 hello.c
```
gcc hello.c -o hello -g
```
gdb主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数即可。
如果没有-g,将看不见程序的函数名和变量名,代替它们的全是运行时的内存地址。
#### 命令行界面
```shell
> gdb
(gdb) file hello # 指定要调试的是哪一个源程序
Reading symbols from hello...done.
(gdb) b 8 # 第八行增加断点
Breakpoint 1 at 0x72a: file hello.c, line 8.
(gdb) i b # 查看断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000000072a in main at hello.c:8
(gdb) b 10 # 第十行增加断点
Breakpoint 2 at 0x747: file hello.c, line 10.
(gdb) i b # 查看断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000000072a in main at hello.c:8
2 breakpoint keep y 0x0000000000000747 in main at hello.c:10
(gdb) r # 运行程序
Starting program: /project/gdb/hello
Breakpoint 1, main (argc=1, argv=0x7fffffffcb48) at hello.c:8
warning: Source file is more recent than executable.
8 int aa = 199; # 断点断到了第8行
(gdb) n # 步过
9 printf("%d\n",aa);
Breakpoint 2, main (argc=1, argv=0x7fffffffcb48) at hello.c:10
(gdb) p aa # 展示aa的值
$1 = 199
(gdb) set aa=999 # 更改变量的值
(gdb) p aa # 展示aa的值
$2 = 999
(gdb) n
999 # 注意这里已经输出了 aa的值 就是我们修改的值 源代码是 printf("%d\n",aa);
9 printf("%d\n",aa);
(gdb) watch aa #设置对变量aa的断点 一旦aa发生变化 就会停止运行程序
Hardware watchpoint 2: aa
(gdb) c
Continuing.
199
Hardware watchpoint 2: aa
Old value = 199
New value = 100 # aa被修改了
main (argc=1, argv=0x7fffffffcb48) at hello.c:11
11 fun1(); # 断在了aa修改的下一行
(gdb) s # 步入 fun1函数
fun1 () at hello.c:3
3 printf("hello\n");
# 两个分支
# 第一个 执行finish语句 结束函数调用
(gdb) finish
Run till exit from #0 fun1 () at hello.c:3
hello
world
hhhhhhhh
0x0000555555554758 in main (argc=1, argv=0x7fffffffcb48) at hello.c:11
11 fun1();
# 第二个 执行return 强制返回
(gdb) return # 强制返回
Make fun1 return now? (y or n) y
#0 0x0000555555554758 in main (argc=1, argv=0x7fffffffcb48) at hello.c:11
11 fun1();
```
## makefile
### 简介
一个工程中的源文件不计数,其按**类型、功能、模块**分别放在若干个目录中,`makefile`定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为`makefile`就像一个Shell脚本一样,其中也可以执行操作系统的命令。`makefile`带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
## 管道
### popen函数
#### 简介
Linux中的popen()函数可以在程序中执行一个shell命令,并返回命令执行的结果。有两种操作模式,分别为读和写。在读模式中,程序中可以读取到命令的输出,其中有一个应用就是获取网络接口的参数。在写模式中,最常用的是创建一个新的文件或开启其他服务等。
```c
// 头文件
#include <stdio.h>
// 函数原型
FILE *popen(const char *command, const char *type);
```
#### 函数说明
popen()函数通过创建一个管道,`调用fork()`产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。
type参数`只能是读或者写中的一种`,得到的返回值(标准I/O流)也具有和type相应的只读或只写类型。如果type是"r"则文件指针连接到command的标准输出;如果type是"w"则文件指针连接到command的标准输入。
command参数是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。
popen()的返回值是个标准I/O流,必须由pclose来终止。前面提到这个流是单向的(只能用于读或写)。向这个流写内容相当于写入该命令的标准输入,命令的标准输出和调用popen()的进程相同;与之相反的,从流中读数据相当于读取命令的标准输出,命令的标准输入和调用popen()的进程相同。
#### 返回值
如果调用fork()或pipe()失败,或者不能分配内存将返回NULL,否则返回标准I/O流。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL。
```c
#include <stdlib.h>
#include <stdio.h>
#define BUF_SIZE 1024
char buf[BUF_SIZE];
int main(void)
{
FILE * p_file = NULL;
p_file = popen("ifconfig eth0", "r");
if (!p_file) {
fprintf(stderr, "Erro to popen");
}
// 读取文件信息 到缓冲区中去
while (fgets(buf, BUF_SIZE, p_file) != NULL) {
fprintf(stdout, "%s", buf); // 输出buf信息到控制台
}
pclose(p_file); // 关闭i/o 流
return 0;
}
```
#### 总结
popen函数包括了至少三个步骤: 创建一个管道、创建一个子进程、启动并调用shell执行命令command指定的命令,这样每个popen的调用将启动两个进程,效率低
```shell
gcc insert.c -o insert `mysql_config --cflags --libs`
```
### pipe函数
## 添加模块
### 模块的意义
由于LINUX设备驱动以内核模块的形式而存在,在具体的设备驱动开发中,将驱动编译为内核模块也有很强的工程意义,因为如果将正在开发中的驱动直接编译入内核,而开发过程中会**不断修改驱动的代码**,则需要**不断地编译内核**并重启内核,但是如果编译为**模块**,则只需要**rmmod**并**insmod**即可,开发效率大为提高。下面说明如何添加、编译并允许LINUX模块。
### 模块的组成
LINUX的模块主要由6部分组成:
1. 模块的加载函数(必须)
当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
2. 模块的卸载函数(必须)
当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。
3. 模块许可证声明
模块许可证(LICENSE)声明描述内核模块的的许可权限,如果不声明LICENSE,模块被加载时,将接到内核被污染的警告。
4. 模块参数(可选)
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。
5. 模块导出符号(可选)
内核模块可以导出符号(symbol,对应于函数或者是变量),这样其他模块就可以使用本模块中的变量或者是函数。
6. 模块作者等信息声明(可选)
### demo
#### 模块代码 kello.c
此模块的主要功能就是分别在模块装载和模块卸载的时候打印输出内容
```c
#include <linux/module.h>
int kello_init( void )
{
printk( "\n Hello, students from SDUST! This is in kernel space! \n" );
return 0;
}
void kello_exit( void )
{
printk( "\n Goodbye now... students from SDUST! \n" );
}
MODULE_AUTHOR("SDUSTOS <fangs99@126.com>");
MODULE_LICENSE("GPL");
module_init(kello_init);
module_exit(kello_exit);
```
#### Makefile
`$(MAKE) -C $(KDIR) M=$(PWD)`与`$(MAKE) -C $(KDIR) SUBDIRS =$(PWD)`的作用是等效的,后者是较老的使用方法。推荐使用M而不是SUBDIRS,前者更明确。
**旧版本的Ubuntu14 运行 `$(MAKE) -C $(KDIR) M=$(PWD)`没有问题 但是新版本不行**
```makefile
ifneq ($(KERNELRELEASE),)
obj-m := kello.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(shell pwd) modules
rm -r -f .tmp_versions *.mod.c .*.cmd *.o *.symvers
endif
```
#### 编译执行过程
```shell
> cd /home/yhn/os/ch12/vexp1
> make # 编译模块代码
make -C /lib/modules/5.4.0-42-generic/build M=/home/yhn/os/ch12/vexp1 modules
make[1]: Entering directory '/usr/src/linux-headers-5.4.0-42-generic'
CC [M] /home/yhn/os/ch12/vexp1/kello.o
Building modules, stage 2.
MODPOST 1 modules
CC [M] /home/yhn/os/ch12/vexp1/kello.mod.o
LD [M] /home/yhn/os/ch12/vexp1/kello.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.4.0-42-generic'
rm -r -f .tmp_versions *.mod.c .*.cmd *.o *.symvers
> insmod kello.ko # 安装模块
> dmesg | tail -1 # 显示最后一行
Hello, students from SDUST! This is in kernel space!
> rmmod kello.ko
> dmesg | tail -1 # 显示最后一行
Goodbye now... students from SDUST!
```
#### dmesg命令
dmesg命令用于显示开机信息。
kernel会将开机信息存储在ring buffer中。您若是开机时来不及查看信息,可利用dmesg来查看。开机信息亦保存在/var/log目录中,名称为dmesg的文件里。
持续跟踪dmesg日志文件
` 添加模块和删除模块并不会改变/var/log/dmesg文件的内容,可能是系统还没有写进去`
> tail -f /var/log/dmesg
## proc伪文件系统
/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux内核空间和用户间之间进行**通信**。在 /proc 文件系统中,我们可以将对虚拟文件的**读写**作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的.
许多程序实际上只是从 /proc 的文件中收集信息,然后按照它们自己的格式组织后显示出来。有一些显示进程信息的程序(top、ps 等)就是这么作的
````shell
> ls -alh /proc # 省略了数字文件夹
-r--r--r-- 1 root root 0 Dec 21 17:40 buddyinfo
dr-xr-xr-x 4 root root 0 Dec 15 13:30 bus
-r--r--r-- 1 root root 0 Dec 21 17:40 cgroups
-r--r--r-- 1 root root 0 Dec 21 17:40 cmdline
-r--r--r-- 1 root root 0 Dec 21 17:40 consoles
-r--r--r-- 1 root root 0 Dec 21 17:40 cpuinfo
-r--r--r-- 1 root root 0 Dec 21 17:40 crypto
-r--r--r-- 1 root root 0 Dec 21 17:39 devices
-r--r--r-- 1 root root 0 Dec 21 17:39 diskstats
-r--r--r-- 1 root root 0 Dec 21 17:40 dma
dr-xr-xr-x 2 root root 0 Dec 21 17:40 driver
-r--r--r-- 1 root root 0 Dec 21 17:40 execdomains
-r--r--r-- 1 root root 0 Dec 21 17:40 fb
-r--r--r-- 1 root root 0 Dec 21 17:39 filesystems
dr-xr-xr-x 7 root root 0 Dec 15 13:30 fs
-r--r--r-- 1 root root 0 Dec 21 17:39 interrupts
-r--r--r-- 1 root root 0 Dec 21 17:40 iomem
-r--r--r-- 1 root root 0 Dec 21 17:40 ioports
dr-xr-xr-x 57 root root 0 Dec 15 13:30 irq
-r--r--r-- 1 root root 0 Dec 15 13:30 kallsyms
-r-------- 1 root root 128T Dec 15 13:30 kcore
-r--r--r-- 1 root root 0 Dec 21 17:40 keys
-r--r--r-- 1 root root 0 Dec 21 17:40 key-users
-r-------- 1 root root 0 Dec 15 13:30 kmsg
-r-------- 1 root root 0 Dec 21 17:40 kpagecgroup
-r-------- 1 root root 0 Dec 21 17:40 kpagecount
-r-------- 1 root root 0 Dec 21 17:40 kpageflags
-r--r--r-- 1 root root 0 Dec 21 17:40 loadavg
-r--r--r-- 1 root root 0 Dec 21 17:40 locks
-r--r--r-- 1 root root 0 Dec 21 17:40 mdstat
-r--r--r-- 1 root root 0 Dec 21 17:39 meminfo
-r--r--r-- 1 root root 0 Dec 21 17:40 misc
-r--r--r-- 1 root root 0 Dec 21 17:40 modules
lrwxrwxrwx 1 root root 11 Dec 21 17:40 mounts -> self/mounts
dr-xr-xr-x 3 root root 0 Dec 21 17:40 mpt
-rw-r--r-- 1 root root 0 Dec 15 13:30 mtrr
lrwxrwxrwx 1 root root 8 Dec 21 17:39 net -> self/net
-r-------- 1 root root 0 Dec 21 17:40 pagetypeinfo
-r--r--r-- 1 root root 0 Dec 21 17:40 partitions
dr-xr-xr-x 2 root root 0 Dec 21 17:40 pressure
-r--r--r-- 1 root root 0 Dec 21 17:40 sched_debug
-r--r--r-- 1 root root 0 Dec 21 17:40 schedstat
dr-xr-xr-x 4 root root 0 Dec 15 13:30 scsi
lrwxrwxrwx 1 root root 0 Dec 15 13:30 self -> 83471
-r-------- 1 root root 0 Dec 21 17:40 slabinfo
-r--r--r-- 1 root root 0 Dec 21 17:40 softirqs
-r--r--r-- 1 root root 0 Dec 21 17:39 stat
-r--r--r-- 1 root root 0 Dec 15 13:30 swaps
dr-xr-xr-x 1 root root 0 Dec 15 13:30 sys
--w------- 1 root root 0 Dec 15 13:30 sysrq-trigger
dr-xr-xr-x 2 root root 0 Dec 21 17:40 sysvipc
lrwxrwxrwx 1 root root 0 Dec 15 13:30 thread-self -> 83471/task/83471
-r-------- 1 root root 0 Dec 21 17:40 timer_list
dr-xr-xr-x 4 root root 0 Dec 21 17:39 tty
-r--r--r-- 1 root root 0 Dec 21 17:39 uptime
-r--r--r-- 1 root root 0 Dec 21 17:40 version
-r--r--r-- 1 root root 0 Dec 21 17:40 version_signature
-r-------- 1 root root 0 Dec 21 17:40 vmallocinfo
-r--r--r-- 1 root root 0 Dec 21 17:39 vmstat
-r--r--r-- 1 root root 0 Dec 21 17:39 zoneinfo
````
```
cmdline:系统启动时输入给内核命令行参数
cpuinfo:CPU的硬件信息 (型号, 家族, 缓存大小等)
devices:主设备号及设备组的列表,当前加载的各种设备(块设备/字符设备)
dma:使用的DMA通道
filesystems:当前内核支持的文件系统,当没有给 mount(1) 指明哪个文件系统的时候, mount(1) 就依靠该文件遍历不同的文件系统
interrupts :中断的使用及触发次数,调试中断时很有用
ioports I/O:当前在用的已注册 I/O 端口范围
kcore:该伪文件以 core 文件格式给出了系统的物理内存映象(比较有用),可以用 GDB 查探当前内核的任意数据结构。该文件的总长度是物理内存 (RAM) 的大小再加上 4KB
kmsg:可以用该文件取代系统调用 syslog(2) 来记录内核日志信息,对应dmesg命令
kallsym:内核符号表,该文件保存了内核输出的符号定义, modules(X)使用该文件动态地连接和捆绑可装载的模块
loadavg:负载均衡,平均负载数给出了在过去的 1、 5,、15 分钟里在运行队列里的任务数、总作业数以及正在运行的作业总数。
locks:内核锁 。
meminfo物理内存、交换空间等的信息,系统内存占用情况,对应df命令。
misc:杂项 。
modules:已经加载的模块列表,对应lsmod命令 。
mounts:已加载的文件系统的列表,对应mount命令,无参数。
partitions:系统识别的分区表 。
slabinfo:sla池信息。
stat:全面统计状态表,CPU内存的利用率等都是从这里提取数据。对应ps命令。
swaps:对换空间的利用情况。
version:指明了当前正在运行的内核版本。
```
### 表格快速阅读
| 文件 | 作用 |
| --------- | ------------------------------------------------------------ |
| buddyinfo | 内存管理的信息,主要用来分析内存碎片的,内存分为 2个区域,DMA,DMA32 |
| bus | 该子目录包含您机器上的总线能够找到的所有外设信息。这些信息通常是不可阅读的 |
| cgroups | 可以查看系统支持的cgroup子系统,也可以用来判断系统是否支持cgroup,如果hierarchy项非0说明相应的子系统已经被mount,此时如果再mount这个子系统到其它的目录就可能提示busy错误 |
| cmdline | 启动时传递给kernel的参数信息,boot相关。 |
| consoles | It allows users to see what consoles are currently known to the systemand with what flags.(To see which character device lines are currently used for the system console /dev/console, you may simply look into this file) |
| cpuinfo | 系统中CPU的提供商和相关配置信息; |
| crypto | 系统上已安装的内核使用的密码算法及每个算法的详细信息列表 |
| devices | 系统已经加载的所有块设备和字符设备的信息 |
### 各文件详细解释
#### buddyinfo
内存管理的信息,主要用来分析内存碎片的,内存分为 2个区域,DMA,DMA32
在linux中使用buddy算法解决物理内存的外碎片问题,其把所有空闲的内存,以2的幂次方的形式,分成11个块链表,分别对应为1、2、4、8、16、32、64、128、256、512、1024个页块。
而Linux支持NUMA技术,对于NUMA设备,NUMA系统的结点通常是由一组CPU和本地内存组成,每一个节点都有相应的本地内存,因此buddyinfo 中的Node0表示节点ID;而每一个节点下的内存设备,又可以划分为多个内存区域(zone),因此下面的显示中,对于Node0的内存,又划分类DMA、Normal、HighMem区域。而后面则是表示空闲的区域。
```shell
Node 0, zone DMA 0 1 0 1 1 2 2 1 2 2 2
Node 0, zone DMA32 5499 1249 638 569 190 59 53 12 3 0 0
Node 0, zone Normal 417 289 97 44 60 22 12 5 1 0 0
```
#### bus
该子目录包含您机器上的总线能够找到的所有外设信息。这些信息通常是不可阅读的(各种数字,可以打开但看不懂),不过可以使用外部工具(比如 lspcidrake、lspnp 等)对其中大部分重新格式化。
```shell
> tree /proc/bus
bus/
├── input
│ ├── devices
│ └── handlers
└── pci
├── 00
│ ├── 00.0
│ ├── 01.0
│ ├── 07.0
├── 02
│ ├── 00.0
│ └── 05.0
└── devices
```
##### /proc/bus/input/devices
```shell
I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button"
P: Phys=LNXPWRBN/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXPWRBN:00/input/input0
U: Uniq=
H: Handlers=kbd event0
B: PROP=0
B: EV=3
B: KEY=10000000000000 0
I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input1
U: Uniq=
H: Handlers=sysrq kbd event1 leds
B: PROP=0
B: EV=120013
B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=7
I: Bus=0011 Vendor=0002 Product=0013 Version=0006
N: Name="VirtualPS/2 VMware VMMouse"
P: Phys=isa0060/serio1/input1
S: Sysfs=/devices/platform/i8042/serio1/input/input4
U: Uniq=
H: Handlers=mouse0 event2
B: PROP=0
B: EV=b
B: KEY=70000 0 0 0 0
B: ABS=3
I: Bus=0011 Vendor=0002 Product=0013 Version=0006
N: Name="VirtualPS/2 VMware VMMouse"
P: Phys=isa0060/serio1/input0
S: Sysfs=/devices/platform/i8042/serio1/input/input3
U: Uniq=
H: Handlers=mouse1 event3
B: PROP=1
B: EV=7
B: KEY=30000 0 0 0 0
B: REL=103
I: Bus=0003 Vendor=0e0f Product=0003 Version=0110
N: Name="VMware VMware Virtual USB Mouse"
P: Phys=usb-0000:02:00.0-1/input0
S: Sysfs=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1/2-1:1.0/0003:0E0F:0003.0001/input/input5
U: Uniq=
H: Handlers=mouse2 event4
B: PROP=0
B: EV=17
B: KEY=ff0000 0 0 0 0
B: REL=903
B: MSC=10
```
##### /sys/kernel/debug/usb/device
这个文件是是时时变换的 /proc下面的devices好想是死的
```shell
> pwd
/sys/kernel/debug/usb
> cat devices # 省略了东西 只留下了u盘的信息
T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 10 Spd=480 MxCh= 0
D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
P: Vendor=0951 ProdID=1642 Rev= 1.00
S: Manufacturer=Kingston
S: Product=DT 101 G2
S: SerialNumber=3E1E5A1B64575139354360BF
C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=100mA
I:* If#= 0 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=06 Prot=50 Driver=usb-storage
E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
# 注意 S: Manufacturer=Kingston
# S: Product=DT 101 G2
```
##### /proc/bus/input/handlers
```shell
N: Number=0 Name=rfkill
N: Number=1 Name=kbd
N: Number=2 Name=sysrq (filter)
N: Number=3 Name=mousedev Minor=32
N: Number=4 Name=evdev Minor=64
N: Number=5 Name=joydev Minor=0
N: Number=6 Name=leds
```
#### cgroups
```
> cat cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 10 1 1
cpu 11 1 1
cpuacct 11 1 1
blkio 2 1 1
memory 6 158 1
devices 12 107 1
freezer 4 2 1
net_cls 5 1 1
perf_event 7 1 1
net_prio 5 1 1
hugetlb 3 1 1
pids 8 112 1
rdma 9 1 1
```
#### cmdline
```shell
> cat cmdline
BOOT_IMAGE=/boot/vmlinuz-5.4.0-58-generic root=UUID=bd8d4647-2f91-4cac-adaa-6d58ecef3e26 ro find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US quiet
```
#### consoles
```shell
> cat consoles
tty0 -WU (EC p ) 4:2
```
#### cpuinfo
```shell
processor :系统中逻辑处理核的编号。对于单核处理器,则课认为是其CPU编号,对于多核处理器则可以是物理核、或者使用超线程技术虚拟的逻辑核
vendor_id :CPU制造商
cpu family :CPU产品系列代号
model :CPU属于其系列中的哪一代的代号
model name:CPU属于的名字及其编号、标称主频
stepping :CPU属于制作更新版本
cpu MHz :CPU的实际使用主频
cache size :CPU二级缓存大小
physical id :单个CPU的标号
siblings :单个CPU逻辑物理核数
core id :当前物理核在其所处CPU中的编号,这个编号不一定连续
cpu cores :该逻辑核所处CPU的物理核数
apicid :用来区分不同逻辑核的编号,系统中每个逻辑核的此编号必然不同,此编号不一定连续
fpu :是否具有浮点运算单元(Floating Point Unit)
fpu_exception :是否支持浮点计算异常
cpuid level :执行cpuid指令前,eax寄存器中的值,根据不同的值cpuid指令会返回不同的内容
wp :表明当前CPU是否在内核态支持对用户空间的写保护(Write Protection)
flags :当前CPU支持的功能
bogomips :在系统内核启动时粗略测算的CPU速度(Million Instructions Per Second)
clflush size :每次刷新缓存的大小单位
cache_alignment :缓存地址对齐单位
address sizes :可访问地址空间位数
power management :对能源管理的支持
```
```shell
> cat cpuinfo # 省略了内容 有几个核就会显示多少个
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 158
model name : Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
stepping : 10
microcode : 0xffffffff
cpu MHz : 2807.996
cache size : 9216 KB
physical id : 0
siblings : 1
core id : 0
cpu cores : 1
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 22
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves arat flush_l1d arch_capabilities
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit srbds
bogomips : 5615.99
clflush size : 64
cache_alignment : 64
address sizes : 45 bits physical, 48 bits virtual
power management:
```
#### crypto
```shell
> cat crypto
name : crct10dif
driver : crct10dif-pclmul
module : crct10dif_pclmul
priority : 200
refcnt : 2
selftest : passed
internal : no
type : shash
blocksize : 1
digestsize : 2
name : ghash
driver : ghash-clmulni
module : ghash_clmulni_intel
priority : 400
refcnt : 1
selftest : passed
internal : no
type : ahash
async : yes
blocksize : 16
digestsize : 16
```
```shell
> cat devices # 省略了一些重复内容
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyprintk
6 lp
7 vcs
10 misc
13 input
14 sound/midi
14 sound/dmmidi
21 sg
29 fb
89 i2c
99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 ttyMAX
216 rfcomm
226 drm
241 aux
242 hidraw
243 vfio
244 bsg
245 watchdog
246 ptp
247 pps
248 cec
249 rtc
250 dax
251 dimmctl
252 ndctl
253 tpm
254 gpiochip
Block devices:
2 fd
7 loop
8 sd
9 md
11 sr
65 sd
253 device-mapper
254 mdp
259 blkext
```
### ps -ef命令
```shell
hn@yhnComputer:~$ ps -ef #文本太多 省略了一大部分
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:37 ? 00:00:12 /sbin/init auto noprompt
root 2 0 0 14:37 ? 00:00:00 [kthreadd]
root 3 2 0 14:37 ? 00:00:00 [rcu_gp]
root 4 2 0 14:37 ? 00:00:00 [rcu_par_gp]
root 6 2 0 14:37 ? 00:00:00 [kworker/0:0H-kblockd]
root 9 2 0 14:37 ? 00:00:00 [mm_percpu_wq]
```
### tasklist模块小demo
```c
//-------------------------------------------------------------------
// tasklist.c: 本内核文件创建一个proc伪文件,'/proc/tasklist'
// 通过如下命令可以显示系统中所有进程的部分信息
// 注意:Mkefile文件必须正确放置在当前目录下。
// 编译命令: make
// 内核模块添加:$sudo insmod tasklist.ko
// 添加内核模块后读取并信息tasklist内核信息: $ cat /proc/tasklist
// 内核模块删除:$sudo rmmod tasklist
// NOTE: Written and tested with Linux kernel version 3.13.0 and.3.2.0
// 用户也可以自己完成相关的读写程序,就像课后练习1要求的。我们尤其鼓励同学自己写程序代替cat程序。
// strace函数可用于追踪系统调用,命令格式如下所示:
// $ strace cat /proc/tasklist
//-------------------------------------------------------------------
#include <linux/module.h> // 初始化模块
#include <linux/proc_fs.h> // 创建进程信息入口
#include <linux/sched/task.h> // 初始进程
#include <linux/seq_file.h> // 序列文件
#include <linux/slab.h> // 内存分配释放
#include <linux/sched/signal.h> //下一个进程
char modname[] = "tasklist";
struct task_struct *task;
int taskcounts=0; // 'global' so value will be retained
static void * my_seq_start(struct seq_file *m, loff_t *pos)
{
///printk(KERN_INFO"Invoke start\n"); //可以输出调试信息
if ( *pos == 0 ) // 表示遍历开始
{
task = &init_task; //遍历开始的记录地址
return &task; //返回一个非零值表示开始遍历
}
else //遍历过程中
{
if (task == &init_task ) //重新回到初始地址,退出
return NULL;
return (void*)pos ;//否则返回一个非零值
}
}
static int my_seq_show(struct seq_file *m, void *v)
{//获取进程的相关信息
//printk(KERN_INFO"Invoke show\n");
seq_printf( m, "#%-3d ", taskcounts++ );
seq_printf( m, "%5d ", task->pid );
seq_printf( m, "%lu ", task->state );
seq_printf( m, "%-15s ", task->comm );
seq_puts( m, "\n" );
return 0;
}
static void * my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
//printk(KERN_INFO"Invoke next\n");
(*pos)++;
task= next_task(task); //指向下一个进程
return NULL;
}
static void my_seq_stop(struct seq_file *m, void *v)
{
//printk(KERN_INFO"Invoke stop\n");
// do nothing
}
static struct seq_operations my_seq_fops = {//序列文件记录操作函数集合
.start = my_seq_start,
.next = my_seq_next,
.stop = my_seq_stop,
.show = my_seq_show
};
static int my_open(struct inode *inode, struct file *file)
{
return seq_open(file, &my_seq_fops); //打开序列文件并关联my_seq_fops
}
static const struct file_operations my_proc =
{ //proc文件操作函数集合
.owner = THIS_MODULE,
.open = my_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
int __init my_init( void )
{
struct proc_dir_entry* my_proc_entry;
printk( "<1>\nInstalling \'%s\' module\n", modname );
my_proc_entry = proc_create(modname, 0x644, NULL, &my_proc);//生成proc文件
if (NULL == my_proc_entry)
{
return -ENOMEM;
}
return 0; //SUCCESS
}
void __exit my_exit( void )
{
remove_proc_entry( modname, NULL );//删除proc文件
printk( "<1>Removing \'%s\' module\n", modname );
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
```
## sysfs伪文件系统
是 Linux 内核中设计较新的一种虚拟的基于内存的文件系统,它的作用与 proc 有些类似,但除了与 proc
相同的具有查看和设定内核参数功能之外,还有为 Linux 统一设备模型作为管理之用。相比于 proc 文件系统,使用 sysfs
导出内核数据的方式更为统一,并且组织的方式更好,它的设计从 proc 中吸取了很多教训
文件系统总是**被挂载在 /sys 挂载点上**
```shell
> tree . -L 1
.
├── block
├── bus
├── class
├── dev
├── devices
├── firmware
├── fs
├── hypervisor
├── kernel
├── module
└── power
```
## fork
### 僵尸进程与孤儿进程
#### 孤儿进程
父进程 (pid > 0)1秒后就退出了 ,但是此时子进程(pid = 0)还没有完成,所以没有了父进程子进程成了孤儿进程,在linux中孤儿进程会被init进程收养,成为init的子进程
```c
//orphan_process.c
//演示孤儿进程的生成
//编译命令:$ gcc -o orphan_process orphan_process.c
//运行命令:$ ./orphan_process
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
if((pid=fork())==-1)
perror("fork");
else if(pid==0)
{
//输出子进程pid以及其父进程pid,此时父进程未退出
printf("child : pid=%d,ppid=%d\n",getpid(),getppid());
sleep(2);//休眠两秒等待父进程先退出
//请注意此次输出子进程的ppid,与上一次的差异,此时父进程已退出
printf("child : pid=%d,ppid=%d\n",getpid(),getppid());
}
else
{
printf("father : pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
exit(0);//休眠一秒后直接退出,未使用wait调用。此时子进程相当于被“无情的抛弃了”
}
}
```
#### 僵尸进程
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程
**僵尸进程对系统有害吗?**
**不会**。由于僵尸进程并不做任何事情, 不会使用任何资源也不会影响其它进程, 因此存在僵尸进程也没什么坏处。 不过由于进程表中的退出状态以及其它一些进程信息也是存储在内存中的,因此存在太多僵尸进程有时也会是一些问题。
**你可以想象成这样:**
“你是一家建筑公司的老板。你每天根据工人们的工作量来支付工资。 有一个工人每天来到施工现场,就坐在那里, 你不用付钱, 他也不做任何工作。 他只是每天都来然后呆坐在那,仅此而已!”
这个工人就是僵尸进程的一个活生生的例子。**但是**, 如果你有很多僵尸工人, 你的建设工地就会很拥堵从而让那些正常的工人难以工作。
````c
//zombie_process.c
//演示僵尸进程的生成
//编译命令:$ gcc -o zombie_process zombie_process.c
//运行命令:$ ./zombie_process
//子进程退出后,父进程需要调用wait来处理其遗留的task_struct
//如果父进程未调用wait,那么就会产生僵尸进程。
//在ps命令中,标记为defunct的进程,就是僵尸进程。
//defunct 进程可能会一直留在系统中直到系统重新启动
//“僵尸”进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot)。
//由于进程表的容量是有限的,所以,defunct进程不仅占用系统的内存资源,影响系统的性能,而且如果其数目太多,还会导致系统瘫痪。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
if((pid=fork())==-1)
perror("fork");
else if(pid==0)
{
printf("child_pid pid=%d\n",getpid());
exit(0);
}
sleep(5);
system("ps");//等待5秒后,子进程肯定已经退出,但是父进程一直未调用wait
exit(0);
}
--------------------------
执行结果
child_pid pid=20378
PID TTY TIME CMD
19957 pts/0 00:00:00 su
19959 pts/0 00:00:00 bash
20377 pts/0 00:00:00 zombie_process
20378 pts/0 00:00:00 zombie_process <defunct>
20379 pts/0 00:00:00 sh
20380 pts/0 00:00:00 ps
````
找出僵尸进程
> ps aux | grep defunct
## 线程的创建
### 单个线程的创建
主线程创建一个子线程帮自己算出数来,然后等待结果,之后拿到结果后打印
```c
/*filename mythread_posix1.c:展示posix线程生成函数的使用方法
*编译: $ gcc -o mythread_posix1 mythread_posix1.c -lpthread
*执行: 指定一个累加次数作为参数值,如100000000: $ ./mythread_posix1 100000000
*/
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
pthread_t thread;
int counter=0;
int upper;
// 线程函数,功能是对counter数值进行累加;
//线程函数可以完成更复杂的工作,这仅仅是一个示例
void *my_thread()
{
int i, temp;
printf ("I'm thread 1\n");
for (i = 0; i < upper; i++)
{
temp = counter;
temp += 1;
counter = temp;
}
printf("thread1 :Is Main function waiting for me ?\n");
pthread_exit(NULL);
}
void thread_create()
{
int temp;
memset(&thread, 0, sizeof(thread));
/*create the new thread*/
if((temp = pthread_create(&thread, NULL, my_thread, NULL)) != 0) //创建线程
printf("Creating thread 1 has failed!\n");
else
printf("Thread 1 has been created! \n");
}
void thread_wait(void)
{/*waiting for the thread finished*/
if(thread !=0)
{
pthread_join(thread,NULL);//等待线程退出
printf("Theread 1 has exited! \n");
}
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr,"usage: mythrd-posix1 integer_value\n");
return -1;
}
if (atoi(argv[1]) < 0)
{
fprintf(stderr,"Argument %d must be non-negative\n",atoi(argv[1]));
return -1;
}
upper = atoi(argv[1]);
printf("I am main function, I am creating the threads! \n");
thread_create();
printf("I am main function , I am waiting the threads finished! \n");
thread_wait();
printf("counter = %d\n",counter);
return 0;
}
-------------------------------------------------------------------
执行结果
> ./mythread_posix1 123
I am main function, I am creating the threads!
Thread 1 has been created!
I am main function , I am waiting the threads finished!
I'm thread 1
thread1 :Is Main function waiting for me ?
Theread 1 has exited!
counter = 123
```
### 多个线程的创建(争夺共享资源)
./mythread_posix2 10000
创建4个线程对同一个变量count进行加一操作,最后的答案正常来说应该是4000,但是结果是不确定的。
主要有两个方面的原因:
1. 下方代码中的i也是一个临界资源 ,for循环的执行速度很快,但是一个线程被创建出来并不会立即执行,只有当操作系统调度到它的时候才会执行,当for循环执行完了,i的值已经变成了4 所以有可能最终4个子线程获得的线程是一个地址。
```c
for(i=0; i<THREADNO;i++)
{
//注意创建多个线程时使用的方法
if(temp = pthread_create(&thread[i], NULL, my_thread, &i)) != 0) //注意传递线程参数的方法
printf("Creating thread %d has failed!\n",i);
else
printf("Thread %d has been created! \n",i);
}
```
修改方法
> temp = pthread_create(&thread[i], NULL, my_thread, &i)) != 0
更改为
> temp = pthread_create(&thread[i], NULL, my_thread, (void*)(thread_id+i))) != 0
2. count这个全局变量是一个临界资源
只能通过加锁解决
```c
/*filename mythread_posix2.c:功能1)如何生成多个线程 2)演示多线程的并发;3)多线程的竞争访问;4)线程函数传递参数
*编译: $ gcc -o mythread_posix2 mythread_posix2.c -lpthread
*执行: 指定一个累加次数作为参数值: $ ./mythread_posix2 100000000
*wirtten by Fang Sheng from SDUST, Oct.10 2014
*tested under Ubuntu 14.04LTS
* 本程序用于说明两个问题:1)多个线程并发运行时,资源竞争会造成运行数据错误
* 2) 在线程间传递参数时,要考虑到执行上下文环境的改变,否则参数传递会出现错误。
*/
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#define THREADNO 4
pthread_t thread[THREADNO];
int counter=0; //the shared variable among threads
int upper;
//线程函数
void *my_thread(void *args)
{
int thread_arg;
int i, temp;
thread_arg = *(int *)args;//获取线程函数的参数
printf ("I'm thread %d\n",thread_arg);
for (i = 0; i < upper; i++)
{
temp = counter;
temp += 1;
counter = temp;
}
printf("thread %d :Is Main function waiting for me ?\n",thread_arg);
pthread_exit(NULL);
}
void thread_create(void)
{
int i,temp;
memset(&thread, 0, sizeof(thread));
/*create new threads*/
for(i=0; i<THREADNO;i++)
{
//注意创建多个线程时使用的方法
if((temp = pthread_create(&thread[i], NULL, my_thread, &i)) != 0) //注意传递线程参数的方法
printf("Creating thread %d has failed!\n",i);
else
printf("Thread %d has been created! \n",i);
}
}
void thread_wait(void)
{
/*waiting for the thread finished*/
int i;
for(i=0; i<THREADNO;i++)
{ //注意等待多个线程退出的方法
if(thread[i] !=0)
{
pthread_join(thread[i],NULL);
printf("Theread %d has exited! \n",i);
}
}
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr,"usage: mythrd-posix2 integer_value\n");
return -1;
}
if (atoi(argv[1]) < 0)
{
fprintf(stderr,"Argument %d must be non-negative\n",atoi(argv[1]));
return -1;
}
upper = atoi(argv[1]);
printf("I am main function, I am creating the threads! \n");
thread_create();
printf("I am main function , I am waiting the threads finished! \n");
thread_wait();
printf("counter = %d\n",counter);
return 0;
}
```
```shell
> ./mythread_posix2 10000
I am main function, I am creating the threads!
Thread 0 has been created!
Thread 1 has been created!
I'm thread 2
thread 2 :Is Main function waiting for me ?
Thread 2 has been created!
Thread 3 has been created!
I am main function , I am waiting the threads finished!
Theread 0 has exited!
I'm thread -1803660928
I'm thread -1803660928
thread -1803660928 :Is Main function waiting for me ?
thread -1803660928 :Is Main function waiting for me ?
I'm thread -1803660928
Theread 1 has exited!
thread -1803660928 :Is Main function waiting for me ?
Theread 2 has exited!
Theread 3 has exited!
counter = 32015
```
### 加锁解决共享资源问题
核心点
```c
pthread_mutex_t mut; //锁的声明
pthread_mutex_init(&mut,NULL);//初始化锁
pthread_mutex_destroy(&mut); //销毁锁
pthread_mutex_lock(&mut); //对临界区加锁
pthread_mutex_unlock(&mut);//解锁
```
```c
/*filename mythread_posix3.c:加锁方法实现正确的竞争访问。请对比mythread_posix2.c和请对比mythread_posix2m.c程序
*usage: $ gcc -o mythread_posix3 mythread_posix3.c -lpthread
*usage: $ ./mythread_posix3 100000000
*wirtten by Fang Sheng from SDUST, Oct.10 2014
*tested under Ubuntu 14.04LTS
*本程序的重点在于:互斥锁的初始化方法、使用方法和销毁方法
*/
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#define THREADNO 4
pthread_t thread[THREADNO];
int counter=0; //the shared variable among threads
int upper;
int thread_id[THREADNO];
pthread_mutex_t mut; //锁的声明
void *my_thread(void *args)
{
int thread_arg;
int i, temp;
thread_arg = *(int *)args;
printf ("I'm thread %d\n",thread_arg);
pthread_mutex_lock(&mut); //对临界区加锁
for (i = 0; i < upper; i++)
{
temp = counter;
temp += 1;
counter = temp;
}
pthread_mutex_unlock(&mut);//解锁
printf("thread %d :Is Main function waiting for me ?\n",thread_arg);
pthread_exit(NULL);
}
void thread_create(void)
{
int i,temp;
memset(&thread, 0, sizeof(thread));
/*create new threads*/
for(i=0; i<THREADNO;i++)
{
if((temp = pthread_create(&thread[i], NULL, my_thread, (void*)(thread_id+i))) != 0)
printf("Creating thread %d has failed!\n",i);
else
printf("Thread %d has been created! \n",i);
}
}
void thread_wait(void)
{
/*waiting for the thread finished*/
int i;
for(i=0; i<THREADNO;i++)
{
if(thread[i] !=0)
{
pthread_join(thread[i],NULL);
printf("Theread %d has exited! \n",i);
}
}
}
int main(int argc, char *argv[])
{
int i;
if (argc != 2)
{
fprintf(stderr,"usage: mythrd-posix1 integer_value\n");
return -1;
}
if (atoi(argv[1]) < 0)
{
fprintf(stderr,"Argument %d must be non-negative\n",atoi(argv[1]));
return -1;
}
upper = atoi(argv[1]);
printf("I am main function, I am creating the threads! \n");
for(i=0; i<THREADNO; i++)
thread_id[i] = i;
pthread_mutex_init(&mut,NULL);//初始化锁
thread_create();
printf("I am main function , I am waiting the threads finished! \n");
thread_wait();
pthread_mutex_destroy(&mut); //销毁锁
printf("counter = %d\n",counter);
return 0;
}
-----------------------------------------------------
> ./mythread_posix3 1000
I am main function, I am creating the threads!
Thread 0 has been created!
Thread 1 has been created!
I'm thread 0
thread 0 :Is Main function waiting for me ?
Thread 2 has been created!
Thread 3 has been created!
I am main function , I am waiting the threads finished!
I'm thread 1
thread 1 :Is Main function waiting for me ?
I'm thread 2
thread 2 :Is Main function waiting for me ?
Theread 0 has exited!
Theread 1 has exited!
Theread 2 has exited!
I'm thread 3
thread 3 :Is Main function waiting for me ?
Theread 3 has exited!
counter = 4000
```
### 虚假唤醒
**虚假唤醒会导致 缺少产品的时候还能进行消费**
其实在我理解,其实虚假唤醒的根源在于一个signal会导致多个调用pthread_cond_wait函数等待条件变量的线程。
解释: 当线程被唤醒,但是没有足够的资源让它去执行事件
例子 我们现在有一个生产者-消费者队列和三个线程(1个生产者 2 个消费者)
**1)** 1号线程从队列中获取了一个元素,此时队列变为空。
**2)** 2号线程也想从队列中获取一个元素,但此时队列为空,2号线程便只能进入阻塞(cond.wait()),等待队列非空。
**3)** 这时,3号线程将一个元素入队,并调用cond.notify()唤醒条件变量。
**4)** 处于等待状态的2号线程接收到3号线程的唤醒信号,便准备解除阻塞状态,执行接下来的任务(获取队列中的元素)。
**5)** 然而可能出现这样的情况:当2号线程准备获得队列的锁,去获取队列中的元素时,此时1号线程刚好执行完之前的元素操作,返回再去请求队列中的元素,1号线程便获得队列的锁,检查到队列非空,就获取到了3号线程刚刚入队的元素,然后释放队列锁。
**6)** 等到2号线程获得队列锁,判断发现队列仍为空,1号线程“偷走了”这个元素,所以对于2号线程而言,这次唤醒就是“虚假”的,它需要再次等待队列非空。
**7)** **如果只有一次判断 2号进程这时候不再进行判断,直接从空的缓冲区中取货物,导致负数出现,这是我们不允许的。**
**所以需要反复检查是否有足够的资源让我们进行取产品操作**
知道了虚假唤醒我们就可以写生产者消费者
### pthread_conf_wait函数
使用pthread_cond_wait方式如下:
```c
pthread _mutex_lock(&mutex)
while或if(线程执行的条件是否成立) // 当有多个线程可能同时在等待一个锁的时候 需要使用while而不是if
pthread_cond_wait(&cond, &mutex);
线程执行
pthread_mutex_unlock(&mutex);
```
需要解释的有两点,为什么要加锁,以及为什么可以使用while和if。首先解释第一点,有两个方面,线程在执行的部分访问的是进程的资源,有可能有多个线程需要访问它,为了避免由于线程并发执行所引起的资源竞争,所以要让每个线程互斥的访问公有资源,但是细心一下就会发现,如果while或者if判断的时候,不满足线程的执行条件,那么线程便会调用pthread_cond_wait阻塞自己,但是它持有的锁怎么办呢,如果他不归还操作系统,那么其他线程将会无法访问公有资源。这就要追究一下pthread_cond_wait的内部实现机制,当**pthread_cond_wait被调用线程阻塞**的时候,**pthread_cond_wait会自动释放互斥锁**。释放互斥锁的**时机**是什么呢:是线程从调用pthread_cond_wait到操作系统把他**放在线程等待队列之后**,这样做有一个很重要的原因,就是mutex的第二个作用,保护条件。想一想,线程是并发执行的,如果在没有把被阻塞的线程A放在等待队列之前,就释放了互斥锁,这就意味着其他线程比如线程B可以获得互斥锁去访问公有资源,这时候线程A所等待的条件改变了,但是它没有被放在等待队列上,**导致A忽略了等待条件被满足的信号**。倘若在线程A调用pthread_cond_wait开始,到把A放在等待队列的过程中,都持有互斥锁,其他线程无法得到互斥锁,就不能改变公有资源。这就保证了线程A被放在等待队列上之后才会有公有资源被改变的信号传递给等待队列。
总结而言 执行pthread_conf_wait函数分为3个步骤
1. 解锁 前一步已经加锁了,如果不满足条件不释放锁的话,肯定会导致死锁,
2. 阻塞
3. 再次加锁
### 解决生产者消费者问题
#### 实例代码(只能来30个)
这个实例代码中的只有一个生产者和消费者,不存在虚假唤醒的情况,所以用if也不会出错
```c
//本程序用于演示使用POSIX条件变量解决生产者-消费者问题。
//编译命令: $ gcc -o pro_csm pro_csm.c -lpthread
//执行命令:$./pro_csm
//要点:条件变量的使用;环形缓冲区的使用;防止条件变量的虚假唤醒
//
//测试:可用while循环中的if语句替换上面的while语句,重新执行观察实验结果。
//
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 16 // 缓冲区数量
#define PRO_NO 30 // PRODUCING NO
#define OVER ( - 1) //生产结束标志
#define PSLEEP 10000 // 生产者随机睡眠时间
#define CSLEEP 10000 // 消费者随机睡眠时间
pthread_mutex_t lock; /* 互斥体lock 用于对缓冲区的互斥操作 */
pthread_cond_t notempty; /* 缓冲区非空的条件变量 */
pthread_cond_t notfull; /* 缓冲区未满的条件变量 */
struct prodcons
{// 缓冲区相关数据结构
int buf[BUFFER_SIZE]; /* 实际数据存放的数组*/
int readpos, writepos; /* 读写指针*/
};
struct prodcons buffer;
/* 初始化缓冲区结构 */
void init(struct prodcons *b)
{
b->readpos = 0;
b->writepos = 0;
}
/* 测试:生产者线程将0 到 PRO_NO的整数送入缓冲区,消费者线
程从缓冲区中获取整数,两者都打印信息*/
void *producer(void *data)
{
int n;
for (n = 0; n <= PRO_NO; n++)
{
pthread_mutex_lock(&lock);
/* 等待缓冲区未满,应该用while判断,因为有可能发送虚假唤醒:期待的条件尚不成立的唤醒。*/
// 如果当writepos 和 readpos都等于一个值得时候 要不为空 要不为满
// 一开始 readpos = writepos = 0 所以一开始的生产者不满足while的条件
// 直接取生产产品
while ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos)
//if ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos) //会产生虚假唤醒
{
pthread_cond_wait(¬full, &lock);
}
/* 写数据,并移动指针 */
if (n < PRO_NO)
{
buffer.buf[buffer.writepos] = n;
printf("%d --->\n", n);
usleep(PSLEEP);
}
else
{
buffer.buf[buffer.writepos] = OVER;
printf("%d --->\n", OVER);
}
buffer.writepos++; // 如果缓冲区中有产品 那么 writepos的值一定大于 readpos
if (buffer.writepos >= BUFFER_SIZE)
buffer.writepos = 0;
/* 设置缓冲区非空的条件变量*/
pthread_cond_signal(¬empty);
pthread_mutex_unlock(&lock);
}
return NULL;
}
void *consumer(void *data)
{
int d;
while (1)
{
pthread_mutex_lock(&lock);
/* 等待缓冲区非空,应该用while判断,因为有可能发送虚假唤醒:期待的条件尚不成立的唤醒。*/
while(buffer.writepos == buffer.readpos)
//if(buffer.writepos == buffer.readpos) //会产生虚假唤醒
{
pthread_cond_wait(¬empty, &lock);
}
/* 读数据,移动读指针*/
d = buffer.buf[buffer.readpos];
//usleep(CSLEEP);
buffer.readpos++;
if (buffer.readpos >= BUFFER_SIZE)
buffer.readpos = 0;
/* 设置缓冲区未满的条件变量*/
pthread_cond_signal(¬full);
pthread_mutex_unlock(&lock);
printf("--->%d \n", d);
if (d == OVER)
break;
}
return NULL;
}
int main(void)
{
pthread_t th_c, th_p;
void *retval;
int i;
init(&buffer);
pthread_mutex_init(&lock, NULL);
pthread_cond_init(¬empty, NULL);
pthread_cond_init(¬full, NULL);
/* 创建生产者和消费者线程*/
pthread_create(&th_c, NULL, producer, 0);
pthread_create(&th_p, NULL, consumer, 0);
/* 等待两个线程结束*/
pthread_join(th_c, &retval);
pthread_join(th_p, &retval);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(¬empty);
pthread_cond_destroy(¬full);
return 0;
}
```
#### 自己的代码(可以实现无限重叠 只有一个生产者和消费者)
```c
//本程序用于演示使用POSIX条件变量解决生产者-消费者问题。
//编译命令: $ gcc -o pro_csm pro_csm.c -lpthread
//执行命令:$./pro_csm
//要点:条件变量的使用;环形缓冲区的使用;防止条件变量的虚假唤醒
//
//测试:可用while循环中的if语句替换上面的while语句,重新执行观察实验结果。
//
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 16 // 缓冲区数量
#define PRO_NO 30 // PRODUCING NO
#define OVER ( - 1) //生产结束标志
#define PSLEEP 100000 // 生产者随机睡眠时间
#define CSLEEP 100000 // 消费者随机睡眠时间
pthread_mutex_t lock; /* 互斥体lock 用于对缓冲区的互斥操作 */
pthread_cond_t notempty; /* 缓冲区非空的条件变量 */
pthread_cond_t notfull; /* 缓冲区未满的条件变量 */
struct prodcons
{// 缓冲区相关数据结构
int buf[BUFFER_SIZE]; /* 实际数据存放的数组*/
int readpos, writepos; /* 读写指针*/
};
struct prodcons buffer;
/* 初始化缓冲区结构 */
void init(struct prodcons *b)
{
b->readpos = 0;
b->writepos = 0;
}
/* 测试:生产者线程将0 到 PRO_NO的整数送入缓冲区,消费者线
程从缓冲区中获取整数,两者都打印信息*/
void *producer(void *data)
{
int n=0;
while(1){
pthread_mutex_lock(&lock);
/* 等待缓冲区未满,应该用while判断,因为有可能发送虚假唤醒:期待的条件尚不成立的唤醒。*/
while ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos)
// if ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos) //会产生虚假唤醒
{
pthread_cond_wait(¬full, &lock);
}
/* 写数据,并移动指针 */
buffer.buf[buffer.writepos] = n;
printf("%d --->\n", n);
n++;
usleep(PSLEEP);
buffer.writepos++;
if (buffer.writepos >= BUFFER_SIZE)
buffer.writepos = 0;
/* 设置缓冲区非空的条件变量*/
pthread_cond_signal(¬empty);
pthread_mutex_unlock(&lock);
}
return NULL;
}
void *consumer(void *data)
{
int d;
while (1)
{
pthread_mutex_lock(&lock);
while(buffer.writepos == buffer.readpos)
{
pthread_cond_wait(¬empty, &lock);
}
/* 读数据,移动读指针*/
d = buffer.buf[buffer.readpos];
//usleep(CSLEEP);
buffer.readpos++;
if (buffer.readpos >= BUFFER_SIZE)
buffer.readpos = 0;
/* 设置缓冲区未满的条件变量*/
pthread_cond_signal(¬full);
pthread_mutex_unlock(&lock);
printf("--->%d \n", d);
}
return NULL;
}
int main(void)
{
pthread_t th_c, th_p;
void *retval;
int i;
init(&buffer);
pthread_mutex_init(&lock, NULL);
pthread_cond_init(¬empty, NULL);
pthread_cond_init(¬full, NULL);
/* 创建生产者和消费者线程*/
pthread_create(&th_c, NULL, producer, 0);
pthread_create(&th_p, NULL, consumer, 0);
/* 等待两个线程结束*/
pthread_join(th_c, &retval);
pthread_join(th_p, &retval);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(¬empty);
pthread_cond_destroy(¬full);
return 0;
}
```
#### 多个生产者消费者(没跑通呢)
```c
//本程序用于演示使用POSIX条件变量解决生产者-消费者问题。
//编译命令: $ gcc -o pro_csm pro_csm.c -lpthread
//执行命令:$./pro_csm
//要点:条件变量的使用;环形缓冲区的使用;防止条件变量的虚假唤醒
//
//测试:可用while循环中的if语句替换上面的while语句,重新执行观察实验结果。
//
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 16 // 缓冲区数量
#define PRO_NO 30 // PRODUCING NO
#define OVER ( - 1) //生产结束标志
#define PPNO 3 // 生产者数量
#define CPNO 2 // 消费者数量
#define PSLEEP 100000 // 生产者随机睡眠时间
#define CSLEEP 100000 // 消费者随机睡眠时间
int thread_id1[PPNO] = {1,2,3};
int thread_id2[CPNO] = {4,5};
pthread_mutex_t lock; /* 互斥体lock 用于对缓冲区的互斥操作 */
pthread_cond_t notempty; /* 缓冲区非空的条件变量 */
pthread_cond_t notfull; /* 缓冲区未满的条件变量 */
struct prodcons
{// 缓冲区相关数据结构
int buf[BUFFER_SIZE]; /* 实际数据存放的数组*/
int readpos, writepos; /* 读写指针*/
};
struct prodcons buffer;
/* 初始化缓冲区结构 */
void init(struct prodcons *b)
{
b->readpos = 0;
b->writepos = 0;
}
/* 测试:生产者线程将0 到 PRO_NO的整数送入缓冲区,消费者线
程从缓冲区中获取整数,两者都打印信息*/
void *producer(void *data)
{
int n=0;
while(1){
pthread_mutex_lock(&lock);
/* 等待缓冲区未满,应该用while判断,因为有可能发送虚假唤醒:期待的条件尚不成立的唤醒。*/
while ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos)
// if ((buffer.writepos + 1) % BUFFER_SIZE == buffer.readpos) //会产生虚假唤醒
{
pthread_cond_wait(¬full, &lock);
}
/* 写数据,并移动指针 */
buffer.buf[buffer.writepos] = n;
printf("%d --->\n", n);
n++;
usleep(PSLEEP);
buffer.writepos++;
if (buffer.writepos >= BUFFER_SIZE)
buffer.writepos = 0;
/* 设置缓冲区非空的条件变量*/
pthread_cond_signal(¬empty);
pthread_mutex_unlock(&lock);
}
return NULL;
}
void *consumer(void *data)
{
int d;
while (1)
{
pthread_mutex_lock(&lock);
while(buffer.writepos == buffer.readpos)
{
pthread_cond_wait(¬empty, &lock);
}
/* 读数据,移动读指针*/
d = buffer.buf[buffer.readpos];
//usleep(CSLEEP);
buffer.readpos++;
if (buffer.readpos >= BUFFER_SIZE)
buffer.readpos = 0;
/* 设置缓冲区未满的条件变量*/
pthread_cond_signal(¬full);
pthread_mutex_unlock(&lock);
printf("--->%d \n", d);
}
return NULL;
}
int main(void)
{
//pthread_t th_c, th_p;
pthread_t producerList[PPNO];
pthread_t consumerList[CPNO];
void *retval;
int i;
init(&buffer);
pthread_mutex_init(&lock, NULL);
pthread_cond_init(¬empty, NULL);
pthread_cond_init(¬full, NULL);
/* 创建生产者和消费者线程*/
for (int i = 0 ;i < PPNO;i++){
pthread_create(&producerList[i], NULL, producer, (void*)(thread_id1+i));
}
for (int j = 0 ;j < CPNO;j++){
pthread_create(&consumerList[j], NULL, producer, (void*)(thread_id2+i));
}
//pthread_create(&th_p, NULL, consumer, 0);
// /* 等待两个线程结束*/
for (int i = 0 ;i < PPNO;i++){
pthread_join(producerList[i], &retval);
}
for (int j = 0 ;j < CPNO;j++){
pthread_join(consumerList[i], &retval);
}
// pthread_join(th_p, &retval);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(¬empty);
pthread_cond_destroy(¬full);
return 0;
}
```
linux知识补充下