Linux高并发服务器开发
第1章 Linux系统编程入门
1.1 Linux开发环境搭建
-
安装VMware虚拟机软件
-
下载Ubuntu18镜像文件并在配置完成虚拟机
-
在Linux下输入
sudo apt install openssh-server
-
在Windows下安装Xshell和Xftp,遇到的问题
出现这个问题的原因是XShell配置文件中写入了强制升级时间,这个版本是2017年12月27日发布的;2018年12月25日后就必须升级。经过摸索和踩坑,找到以下两种解决方案。方案一是临时解决方案,方案二是推荐解决方案。
-
修改本地系统时间
把系统时间改到2018年12月25日之前,就可以打开了。但是这只能解燃眉之急,治标不治本,总不能每次要打开Xshell都修改一下本地时间,打开软件后再手动修改回来吧。
所以这个方案可以作为临时的用一下!!
-
修改安装目录下的nslicense.dll
1)用二进制编辑器(UltraEdit、sublime,notepad++的HEX-Editor插件)打开Xshell/Xftp安装目录下的 nslicense.dll(注意备份下这个文件)2)搜索,0F86,修改为 0F83,一般在第869行
3)保存后,替换到原来的dll文件,再次打开xshell6,发现问题解决。
-
最后
教程仅适用本文适用于Xshell6、Xftp6
-
用Xshell连接主机和虚拟机
- 在Linux下输入
sudo apt install net-tools
- 用
ifconfig
查看Linux下的主机IP - 将主机IP输入Xshell
- 在Linux下输入
-
安装vscode
- 安装一系列插件:中文翻译(可选),安装Remote插件
- 更改SSH的Configure设置
- 选择安装C++拓展插件,配置编译器 详解Linux下使用vscode编译运行和调试C/C++ - 知乎 (zhihu.com)
1.2 GCC
工作流程
gcc -E a.c -o a.i # 预处理
gcc -S a.i -o a.s # 编译
gcc -c a.s -o a.o # 汇编
gcc a.o b.o -o a.out # 链接
gcc a.c -l pthread -o a # 手动添加链接库
-o # 指定生成文件名称
-L # 指定包含库的的搜索目录
-I # 指定 include 包含文件的搜索目录
-g # 生成调试信息,用于 GDB
-D # 指定一个宏
-w # 不生成警告
-Wall # 生成所有警告
-On # 优化等级 0-3(0没有,1默认)
-std # 指定 C 方言,如 -std = C99
我们看下 -D
,这个的作用就是指定一个宏:
#include<stdio.h>
int main()
{
#ifdef DEBUG
printf("log out.\n");
#endif
printf("Hello.\n");
return 0;
}
如果我们加入了 -D DEBUG
那么就会输出 log
,否则,不会输出。
可以认为 g++ = gcc -lstdc++
链接了 C++
库。
1.3 静态库的制作和使用
1.3.1 命名规则
- Linux:libxxx.a
- Windows:libxxx.lib
优点:
- 安全,可以不用提供源程序,保护知识产权
1.3.2 静态库的制作
- gcc获得.o文件
- 将.o文件打包,使用ar工具(archive)
# 源文件真的很简单,所以就不放了
➜ qfc tree # 一个项目的一般结构
.
├── include
│ └── head.h
├── lib
├── main.c
└── src
├── add.c
├── div.c
├── mult.c
└── sub.c
3 directories, 6 files
➜ qfc cd src
➜ src gcc -c add.c div.c sub.c mult.c -I ../include
➜ src ar rcs libcalc.a add.o div.o mult.o sub.o # ar 就是生成静态库的命令
➜ src mv libcalc.a ../lib
➜ src cd ..
➜ qfc gcc main.c -o calculate -I ./include -L ./lib -l calc
➜ qfc ./calculate
2 + 2 = 4
2 - 2 = 0
2 * 2 = 4
2 / 2 = 1
# 只需要提供 include 和 lib ,src 自己保存就好
1.4 动态库的制作和使用
1.4.1 命名规则
- Linux:libxxx.so
- Windows:libxxx.dll
优点:
- 安全,可以不用提供源程序,保护知识产权
1.4.2 动态库的制作
- gcc获得.o文件
- 将.o文件打包,使用gcc命令
➜ qfc tree
.
├── include
│ └── head.h
├── lib
├── main.c
└── src
├── add.c
├── div.c
├── mult.c
└── sub.c
3 directories, 6 files
➜ qfc src
➜ src gcc -c -fPIC *.c -I ../include # 得到和位置无关的 .o 文件
➜ src gcc -shared *.o -o libcalc.so # 制作动态库
➜ src mv libcalc.so ../lib
➜ src cd ..
➜ qfc gcc main.c -o main -I ./include -L lib -l calc # 只加载了动态库信息
➜ qfc ldd calculate # 检查动态库依赖关系
linux-vdso.so.1 (0x00007fff45de1000)
libcalc.so => not found # 找不到
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa62d052000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa62d288000)
➜ qfc ./main
./main: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
对于 elf
格式的可执行程序,是由 ld-linux.so
(动态载入器)来完成的,它先后搜索 elf
文件的 DT_RPATH
段 ——> 环境变量 LD_LIBRARY_PATH
——> /etc/ld.so.cache
文件列表 ——> /lib/,/usr/lib
目录找到库文件后将其载入内存。
# 终端临时添加
➜ qfc pwd
/home/ceyewan/CodeField/CODE_CPP/qfc
➜ qfc export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ceyewan/CodeField/CODE_CPP/qfc/lib
➜ qfc ldd main
linux-vdso.so.1 (0x00007ffc00dff000)
libcalc.so => /home/ceyewan/CodeField/CODE_CPP/qfc/lib/libcalc.so (0x00007fb9f2606000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb9f23d7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb9f2612000)
➜ qfc ./main
2 + 2 = 4
2 - 2 = 0
2 * 2 = 4
2 / 2 = 1
# 用户级别
# 将 export 命令加入到 ~/.bashrc 并 source 使之生效
# 系统级别
# 将 export 命令到 /etc/profile 并 source 使之生效
# 也可在 /etc/ld.so.conf 中直接添加路径
- 静态库:
GCC
进行链接时,会把静态库中代码打包到可执行程序中- 优点,使用简单
- 缺点,造成可执行文件体积庞大,重复加载等
- 动态库:
GCC
进行链接时,动态库的代码不会被打包到可执行程序中- 缺点:使用复杂
- 优点:无需加载,速度快,共享使用(加载到内存后可以供多个进程使用)
1.5 静态库和动态库的对比、
1.5.1 制作过程
1.5.2 优缺点
-
静态库:
GCC
进行链接时,会把静态库中代码打包到可执行程序中- 优点,使用简单,移植方便
- 缺点,造成可执行文件体积庞大,重复加载等
-
动态库:
GCC
进行链接时,动态库的代码不会被打包到可执行程序中- 缺点:使用复杂
- 优点:无需加载,速度快,共享使用(加载到内存后可以供多个进程使用)
1.6 Makefile
作用: 自动化编译,编译顺序、重新编译等复杂功能。
xxx[目标]: xxx[依赖]
xxx[shell 命令]
变量:
- 自定义变量 变量名=变量值
var=hello
AR
:归档维护程序的名称,默认为ar
CC
:C
编译器的名称,默认值为cc
CXX
:C++
编译器的名称,默认值为g++
$@
:目标的完整名称$<
:第一个依赖的文件的名称$^
:所有的依赖文件- 获取变量的值,
$(变量名)
src=sub.c add.c mult.c div.c
target=calculate
$(target): $(src)
$(CC) $^ -o $@
模式匹配:
%
:通配符,匹配一个字符串,%.o: %.c
,两个%
匹配的是同一个字符串。
%.o:%.c
gcc -c $< -o $@
函数:
-
$(wildcard PATTERN...)
- 获取指定目录下指定类型的文件列表
$(wildcard *.c ./src/*.c)
得到当前目录和src
目录下的.c
文件
-
$(patsubst <pattern>,<replacement>,<text>)
- 查找
中的单词是否符合模式
,如果匹配的话,则以 `` 替换 $(patsubst %.c, %.o, a.c b.c)
返回a.o
和b.o
- 查找
clean
功能:
.PHONY:clean
clean:
rm *.o -f
编译makefile:
src=$(wildcard *.c ./src/*.c)
objs=$(patsubst %.c, %.o, $(src))
target=calculate
$(target):$(objs)
$(CC) $^ -o $(target) -I ./include
%.o: %.c
$(CC) -c $< -o $@ -I ./include
.PHONY:clean
clean:
rm -f $(objs)
执行结果:
➜ qfc make
cc -c main.c -o main.o -I ./include
cc -c src/add.c -o src/add.o -I ./include
cc -c src/div.c -o src/div.o -I ./include
cc -c src/mult.c -o src/mult.o -I ./include
cc -c src/sub.c -o src/sub.o -I ./include
cc main.o src/add.o src/div.o src/mult.o src/sub.o -o calculate -I ./include
➜ qfc make clean
rm main.o ./src/add.o ./src/div.o ./src/mult.o ./src/sub.o
参考资料:
https://seisman.github.io/how-to-write-makefile/index.html
1.7 GDB
1.7.1 准备工作:
- 关闭
-O
优化,开启-Wall
显示更多的warning
- 开启
-g
选项,在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb
能找到源文件。
1.7.2 GDB命令:
- 启动
gdb 可执行程序
- 退出
quit/q
- 给程序设置参数,
set args 10 20
,显示参数,show args
- 使用帮助
help [xxx]
- 查看当前文件代码
list/l [xx](行号/函数名/文件名:行号/文件名:函数名)
- 设置显示的行数,
show[set] list/listsize
- 设置断点,
break/b [xx](行号/函数名/文件名:行号/文件名:函数名)
- 查看断点,
info/i b/break
- 删除断点,
d/del/delete 断点编号
- 设置断点无效/生效,
dis/disable[ena/enable] 断点编号
- 设置条件断点,一般用于循环,
b 10 if i==5
- 运行
gdb
程序,start(程序停在第一行)
,run(遇到断点才停)
- 继续运行,下个断点停,
continue/c
- 向下执行一行代码,不进入函数体,
next/n
- 向下单步调试,进入函数体,
step/s
,跳出函数体,finish
- 变量输出,
print/p 变量名
,ptype 变量名
- 自动变量操作,自动打印指定变量的值,
display 变量名
- 修改变量的值,
set var 变量名=变量值
;跳出循环,until
1.8 标准C库IO函数和Linux系统IO函数对比
FILE *fopen(char *filename, char *mode);
int open(const char *path, int access,int mode);
/* open 是系统提供的,fopen 是标准 C 库函数 fopen 是封装了 open 的具有缓冲区的更高一级的函数,可以跨平台使用*/
1.8.1 标准C库的IO函数
1.8.2 标准C库IO和Linux系统IO的关系
1.9 虚拟地址空间
分为内核区和用户区,虚拟内存由操作系统管理。
1.10 文件描述符
内核区的进程控制块(PCB),维护文件描述符表,我们可以通过内核区的 PCB
中的文件描述符表查找文件描述符来定位文件的位置。
前 3 个文件描述符,默认已经打开
0 -> STDIN 1 -> STDOUT 2 -> STDERR
1.11 Linux系统IO函数
1.11.1 open函数
# man open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
// pathname: 要打开文件的路径
// flags: 操作权限(O_RDONLY, O_WRONLY, O_RDWR)
// 返回文件描述符,如果出错就返回 -1 ,c错误信息可以使用 perror 查看
int open(const char *pathname, int flags, mode_t mode);
// mode: 一个八进制的数,表示对创建文件的权限(一般用于创建文件)777 表示最高权限(3种不同类型的用户的权限)
如果打开一个不存在的文件,
int fd = open("log.txt", O_RDONLY);
// int fd = open("log.txt", O_RDONLY | O_CREAT, 0777);
// 0777 作用是指定文件本身的权限, flags 是指定程序对文件的权限
if (fd == -1) {
perror("open");
}
close(fd);
输出结果为:
➜ qfc ./a.out
open: No such file or directory
1.11.2 read 和 write 函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
// fd 文件描述符
// buf 存储读取内容的缓冲区
// 一次读取的数量
// 返回实际读取的数量,-1 表述出错
ssize_t write(int fd, const void *buf, size_t count);
// buf 存储写入内容的缓冲区
// 一次写入的数量
// 返回实际写入的数量,-1 表述出错
int srcfd = open("src.txt", O_RDONLY);
assert(srcfd != -1);
int destfd = open("copy.txt", O_WRONLY | O_CREAT, 0777);
assert(destfd != -1);
char* buffer = (char*)malloc(sizeof(char) * 12);
while (1) {
int rc = read(srcfd, buffer, 10);
assert(rc >= 0);
if (rc == 0) {
return 0;
}
rc = write(destfd, buffer, rc); // 上面读 rc 个,这里写 rc 个
assert(rc >= 0);
}
close(srcfd);
close(destfd);
return 0;
1.11.3 lseek 函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/* fd 文件描述符
offset 文件指针的移动量
whence
- SEEK_SET,起始偏移量为 0
- SEEK_CUR,起始偏移量为当前位置
- SEEK_END,起始偏移量为最后位置
返回值,文件指针移动后的位置
*/
char buf[6];
int fd = open("src.txt", O_RDWR);
assert(fd != -1);
// 移动文件指针到文件头, 读取 5 个字符
lseek(fd, 0, SEEK_SET);
read(fd, buf, 5);
printf("%s\n", buf);
// 此时位置为 5 ,我们跳过 " ",偏移一位
lseek(fd, 1, SEEK_CUR);
read(fd, buf, 5);
printf("%s\n", buf);
// 文件指针从最后回退 6 个字符(包括结尾符,'\0'
lseek(fd, -6, SEEK_END);
read(fd, buf, 5);
printf("%s\n", buf);
// 拓展文件大小,可用于提前申请空间
lseek(fd, 100, SEEK_END);
write(fd, " ", 1);
close(fd);
return 0;
执行结果如下:
➜ qfc touch src.txt
➜ qfc echo "hello world" > src.txt
➜ qfc ll
-rw-r--r-- 1 ceyewan ceyewan 12 6月 18 23:41 src.txt
➜ qfc gcc a.c
➜ qfc ./a.out
hello
world
world
➜ qfc ll
-rw-r--r-- 1 ceyewan ceyewan 113 6月 18 23:52 src.txt
1.11.4 stat和lstat函数
int stat(const char *pathname, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
- 对于普通文件,这两个函数没有区别
- 对于连接文件,
lstat
获取的是链接文件本身的属性,stat
获取的是链接文件指向文件的属性信息
1.11.5 模拟实现ls-l命令
打开当前目录,然后遍历目录,调用 showFile
函数显示该目录下所有文件的信息,文件夹同样是文件,一切皆文件。
void showFiles(const char *pathname)
{
DIR *dir = opendir(pathname);
struct dirent *d;
while ((d = readdir(dir)) != NULL)
{
if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0)
{
continue;
}
showFile(d->d_name);
}
closedir(dir);
}
读取文件信息,最复杂的就是权限信息的显示了,
void showFile(const char *filename)
{
struct stat st;
int rc = stat(filename, &st);
if (rc == -1)
{
perror("stat");
exit(-1);
}
char perms[11];
switch (st.st_mode & S_IFMT)
{
case S_IFLNK:
perms[0] = '1';
break;
case S_IFDIR:
perms[0] = 'd';
break;
case S_IFREG:
perms[0] = '_';
break;
case S_IFBLK:
perms[0] = 'b';
break;
case S_IFCHR:
perms[0] = 'c';
break;
case S_IFSOCK:
perms[0] = 's';
break;
case S_IFIFO:
perms[0] = 'p';
break;
default:
perms[0] = '?';
break;
}
// 文件所有者
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
// 文件所在组
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
// 其他人
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
// 硬连接数
int linkNum = st.st_nlink;
// 文件所有者
char *fileUser = getpwuid(st.st_uid)->pw_name;
// 文件所在组
char *fileGrp = getgrgid(st.st_gid)->gr_name;
// 文件大小
long int fileSize = st.st_size;
// 获取修改的时间,将秒数转化为时间
char *time = ctime(&st.st_mtime);
time[strlen(time) - 1] = '\0'; // 去掉最后的换行符
printf("%s %d %s %s %ld %s %s\n", perms, linkNum, fileUser, fileGrp, fileSize,
time, filename);
}
最后的结果如下,大体上是一样的:
➜ qfc ./ls
_rw-r--r-- 1 ceyewan ceyewan 3738 Sun Jun 19 13:49:26 2022 b.c
drwxr-xr-x 2 ceyewan ceyewan 4096 Sat Jun 18 16:46:47 2022 gdb
drwxr-xr-x 5 ceyewan ceyewan 4096 Sat Jun 18 15:57:27 2022 calculate
_rw-r--r-- 1 ceyewan ceyewan 2478 Sun Jun 19 14:13:59 2022 a.c
_rwxr-xr-x 1 ceyewan ceyewan 16640 Sun Jun 19 14:14:01 2022 ls
drwxr-xr-x 2 ceyewan ceyewan 4096 Sat Jun 18 22:14:31 2022 .vscode
_rwxr-xr-x 1 ceyewan ceyewan 16544 Sun Jun 19 14:00:31 2022 a.out
➜ qfc ls -l
总用量 56
-rw-r--r-- 1 ceyewan ceyewan 2478 6月 19 14:13 a.c
-rwxr-xr-x 1 ceyewan ceyewan 16544 6月 19 14:00 a.out
-rw-r--r-- 1 ceyewan ceyewan 3738 6月 19 13:49 b.c
drwxr-xr-x 5 ceyewan ceyewan 4096 6月 18 15:57 calculate
drwxr-xr-x 2 ceyewan ceyewan 4096 6月 18 16:46 gdb
-rwxr-xr-x 1 ceyewan ceyewan 16640 6月 19 14:14 ls
1.11.6 文件属性操作函数
int access(const char *pathname, int mode);
// 检查是否对文件有 mode 的权限,是返回 0 ,否 -1
int chmod(const char *filename, int mode);
// 修改文件权限为 mode
int chown(const char *path, uid_t owner, gid_t group);
// 改变文件所有者
int truncate(const char *path, off_t length);
// 改变 path 文件的大小为 length
1.11.7 目录操作函数
int mkdir(const char *pathname, mode_t mode);
// pathname: 创建目录的路径
// mode: 权限
int rmdir(const char *pathname);
int rename(const char *oldpath, const char *newpath);
int chdir(const char *path);
// 改变当前工作目录为 path
char *getcwd(char *buf, size_t size);
// 返回当前工作目录,参数可为 null ,buf 中也存储返回结果
1.11.8 目录遍历函数
DIR *opendir(const char *name);
// 打开目录返回目录流(目录里面有很多东西
struct dirent *readdir(DIR *dirp);
// 一个从目录流中读取一个文件
int closedir(DIR *drip);
// 关闭目录流
struct dirent {
// 此目录进入点的inode
ino_t d_ino;
// 目录文件开头至此目录进入点的位移
off_t d_off;
// d_name 的长度, 不包含NULL字符
unsigned short int d_reclen;
// d_name 所指的文件类型
unsigned char d_type;
// 文件名
char d_name[256];
};
/* d_type
DT_BLK - 块设备 DT_CHR - 字符设备
DT_DIR - 目录 DT_LNK - 软连接
DT_FIFO - 管道 DT_REG - 普通文件
DT_SOCK - 套接字 DT_UNKNOWN - 未知 */
1.11.9 dup 和 dup2 函数
int dup(int oldfd);
// 复制文件描述符,返回值和 oldfd 指向同一文件,共享文件指针
int dup2(int oldfd, int newfd);
// 重定向文件描述符,关闭 newfd 指向的文件,然后 newfd 指向 oldfd 指向的文件,返回 newfd
1.11.10 fcntl 函数
int fcntl(int fd, int cmd, ... /* args */);
/* fd : 文件描述符
cmd:表示对文件描述符进行如何操作
- F_DUPFD 复制文件描述符,等价于 dup
fcntl(fd, F_DUPFD);
- F_GETFL 获取文件描述符的 flag(就是 open 时指定的 flag)
int flag = fcntl(fd, F_GETFL);
- F_SETFL 设置文件描述符文件状态 flag
- 必选项:O_PDONLY O_WRONLY O_RDWR
- 可选项:O_APPEND追加数据
NONBLOCK 设置成非阻塞
阻塞和非阻塞:描述的是函数的行为
*/
int fd = open("a.txt", O_RDWR);
int flag = fcntl(fd, F_GETFL);
fcntl(fd, G_SETFL, flag | O_APPEND); // 设置成追加写入
第二章 Linux多进程开发
2.1 进程概述
-
进程是正在运行的程序的实例,是基本的分配单元也是基本的执行单元。
-
可以用一个程序来创建多个进程,进程是由内核定义的抽象实体,并为该实体分配用以执行程序的各项系统资源。
-
单道程序:计算机内存中只允许一个程序运行
-
多道程序:计算机内存中同时放几道相互独立的程序,使它们在调度下,相互穿插运行。
-
时间片:操作系统分配给每个正在运行的进程微观上的一段时间。
-
并行:指在同一时刻,有多条指令在多个处理器上同时执行。
- 并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
- 内核为每个进程分配一个
PCB
进程控制块,维护进程相关的信息,Linux
内核的进程控制块是task_struct
结构体(/usr/src/linux-headers-xxx/include/linux/sched.h
中)
2.2 进程状态转换
2.2.1 进程的状态
- 三态模型:运行态、就绪态、阻塞态
- 五态模型:新建态、运行态、就绪态、阻塞态、终止态
2.2.2 查看进程
ps aux / ajx
- a:显示终端上的所有进程,包括其他用户的进程
- u:显示进程的详细信息
- x:显示没有控制终端的进程
- j:列出与作业控制相关的信息
2.2.3 STAT 参数的意义
- D:不可中断
- R:正在运行或在队列中的进程
- S:处于休眠状态
- T:停止或被追踪
- Z:僵尸进程
- W:进入内存交换
- X:死掉的进程
- <:高优先级
- N:低优先级
- S:包含子进程
- +:位于前台的进程组
2.2.4 实时显示进程动态
top [-d 5]
- -d 来指定信息更新的时间间隔
- M 根据内存使用量排序
- P 根据 CPU 占有率排序
- T 根据进程运行时间排序
- U 根据用户名排序
- K 输入指定的 PID 杀死进程
2.2.5 杀死进程
kill [-signal] pid
kill -l
列出所有信号kill –SIGKILL pid
kill -9 pid
killall name xxx
根据进程名杀死进程
2.2.6 进程号和相关函数
- 每个进程都对应唯一一个进程号,类型为
pid
- 任何进程(除
init
进程)都由一个进程创建,该进程称为被创建进程的父进程PPID
- 进程组是一个或多个进程的集合,有个进程组号,为
PGID
。 pid_t getpid(void)
和pid_t getppid(void)
和pid_t getpgid(pid_t pid)
2.3 进程创建
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid = fork();
if (pid>0)//返回值大于0就是当前进程的父进程
{
printf("pid:%d\n",pid);
printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
}else if (pid==0)//返回值等于0就是当前进程的ID
{
printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
}
for (int i = 0; i < 3; i++)
{
printf("i=%d\n",i);
sleep(1);
}
}
2.4 父子进程
2.4.1 父子进程虚拟地址空间情况
完全的复制操作,可以认为只有 pid
不同, 相互之间几乎没有联系。(准确来说,Linux
的 fork()
使用的是写时拷贝,fork
时并不复制整个地址空间,而是共享,只有当需要写入时才会复制。这样效率高)
2.4.2 父子进程关系及GDB多线程调试
2.4.2.1 父子进程关系
fork
函数的返回值不同PCB
中的进程号和信号集等不同- 数据读时共享,写时拷贝
2.4.2.2 GDB 调试
- 设置调试子进程,
set follow-fork-mode child
,默认是父进程 - 设置调试模式,
set detch-on-fork on/off
,默认为on
,表示调试当前进程时其他进程继续运行;否则,挂起。 - 查看调试的进程,
info inferiors
- 切换当前调试的进程:
inferior id
- 使进程脱离 GDB 的调试:
detach inferiors id
2.5 exec函数族
printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
}else if (pid==0)//返回值等于0就是当前进程的ID
{
printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
}
for (int i = 0; i < 3; i++)
{
printf("i=%d\n",i);
sleep(1);
}
}
### 2.4 父子进程
#### 2.4.1 父子进程虚拟地址空间情况
完全的复制操作,可以认为只有 `pid` 不同, 相互之间几乎没有联系。(准确来说,`Linux` 的 `fork()` 使用的是写时拷贝,`fork` 时并不复制整个地址空间,而是共享,只有当需要写入时才会复制。这样效率高)
#### 2.4.2 父子进程关系及GDB多线程调试
##### 2.4.2.1 **父子进程关系**
- `fork` 函数的返回值不同
- `PCB` 中的进程号和信号集等不同
- 数据读时共享,写时拷贝
##### 2.4.2.2 **GDB 调试**
- 设置调试子进程,`set follow-fork-mode child` ,默认是父进程
- 设置调试模式,`set detch-on-fork on/off`,默认为 `on` ,表示调试当前进程时其他进程继续运行;否则,挂起。
- 查看调试的进程,`info inferiors`
- 切换当前调试的进程:`inferior id`
- 使进程脱离 GDB 的调试:`detach inferiors id`
### 2.5 exec函数族