Linux高并发服务器开发(持续更新中)

Linux高并发服务器开发

在这里插入图片描述

第1章 Linux系统编程入门

1.1 Linux开发环境搭建

  1. 安装VMware虚拟机软件

  2. 下载Ubuntu18镜像文件并在配置完成虚拟机

  3. 在Linux下输入sudo apt install openssh-server

  4. 在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

  1. 用Xshell连接主机和虚拟机

    • 在Linux下输入sudo apt install net-tools
    • ifconfig查看Linux下的主机IP
    • 将主机IP输入Xshell
  2. 安装vscode

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
  • CCC 编译器的名称,默认值为 cc
  • CXXC++ 编译器的名称,默认值为 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.ob.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  618 23:41 src.txt
➜  qfc gcc a.c 
➜  qfc ./a.out 
hello
world
world
➜  qfc ll
-rw-r--r-- 1 ceyewan ceyewan  113  618 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;     //最后一次改变时间(指属性)
};
  1. 对于普通文件,这两个函数没有区别
  2. 对于连接文件,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);
}

读取文件信息,最复杂的就是权限信息的显示了,

image-20220619140904963

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  619 14:13 a.c
-rwxr-xr-x 1 ceyewan ceyewan 16544  619 14:00 a.out
-rw-r--r-- 1 ceyewan ceyewan  3738  619 13:49 b.c
drwxr-xr-x 5 ceyewan ceyewan  4096  618 15:57 calculate
drwxr-xr-x 2 ceyewan ceyewan  4096  618 16:46 gdb
-rwxr-xr-x 1 ceyewan ceyewan 16640  619 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 不同, 相互之间几乎没有联系。(准确来说,Linuxfork() 使用的是写时拷贝,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函数族













































  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值