Linux多线程、多进程、网络编程常见问题

Linux多进程、多线程、网络编程常见问题

入门

1、GCC的工作流程?

工作流程:

预处理、编译、汇编、链接

具体流程如下:

hhh

2、gcc常用的参数选项

在这里插入图片描述

3、Makefile介绍

3.1、Makefile文件命令和规则

  1. 文件命名:makefile 和Makefile

  2. 一个Makefile文件中可以有一个或者多个规则

    目标  ...  : 依赖
    	命令(shell命令)
    	...
    

    目标:最终要生成的文件(伪目标除外)

    依赖:生成目标所需要的文件或是目标

    命令:通过执行命令对依赖操作生成目标(命令前必须Tab缩进)

    Makefile中的其他规则一般是为第一条规则服务的

3.2、基本原理

  • 命令在执行之前,需要先检查规则中的依赖是否存在
    • 如果存在,执行命令
    • 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
  • 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
    • 如果依赖的时间比目标的时间晚,需要重新生成目标
    • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行

3.3、变量

  1. 自定义变量

    var=liu
    #获取变量的值  $(变量名)
    $var
    
  2. 预定义变量

    • AR:归档维护程序的名称,默认值为ar
    • CC:C编译器的名称,默认值为cc
    • CXX:C++编译器的名称,默认是为g++
    • $@:目标的完整名称
    • $<:第一个依赖文件的名称
    • $^:所有的依赖文件
    app:main.c  a.c  b.c
    	gcc -c main.c  a.c  b.c -o app
    	
    # 自动变量只能在规则的命令中使用
    app:main.c  a.c  b.c
    	$(CC) -c $^ -o $@
    

3.4、模式匹配

sub.o:sub.c
	gcc -c sub.c -o sub.o

add.o:add.c
	gcc -c add.c -o add.o

mult.o:mult.c
	gcc -c mult.c -o mult.o

div.o:div.c
	gcc -c div.c -o div.o

main.o:main.c
	gcc -c main.c -o main.o

%.o : %.c

%通配符, 匹配一个字符串

两个%匹配的是同一个字符串

%.o:%.c
	$(CC) -c $< -o $@

3.5、Makefile中部分的函数

$(wildcard PATTERN…)

  • 功能:获取指定目录下指定类型的文件列表
  • 参数:pattern指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
  • 返回:得到的若干文件的文件列表,文件名之间使用空格间隔
  • 示例:
$(wildcard .c ./sub/.c)
返回值格式: a.c b.c c.c d.c e.c f.c

$(patsubst ,)

  • 功能:查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式,如果匹配的话,则以 替换。
  • 可以包含通配符%,表示任意长度的字符串。
  • 返回:函数返回被替换后的字符串
  • 示例:
$(patsubst %.c, %.o, x.c bar.c)
返回值格式: x.o bar.o

使用上面两种方案:

src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(objs)
	$(CC) $(objs) -o $(target)

4、GDB

一款C、C++的调试工具。

通过在编译程序时候后面加上-g,表示打开调试选项。

4.1、命令

在这里插入图片描述

5、静态库和动态库

库文件有两种,静态库和动态库(共享库),

区别是:静态库在程序的链接阶段被复制到程序中;动态库在链接阶段没有被复制在程序中,而是程序在运行时由系统动态加载到内存中供程序调用。

好处: 代码保密; 方便部署和分发

工作原理

静态库:进行链接时,会把静态库中代码打包到程序中

动态库:不会加载在程序中。

程序启动之后,动态库会被加载到内存中,通过ldd命令检查动态库依赖关系

如何定位共享库文件呢?(就是程序如何找共享库的位置,曾经遇到过)

当系统加载可执行代码时候,能够知道其所依赖库的名字,但是还需要知道绝对路径,此时需要系统的动态载入来获取绝对路径。对于程序它的搜索路径是按照下面的顺序来搜索的。

elf文件的DT_RPATH段----> 环境变量LD_LIBRARY_PATH ---->/etc/ld.so.cache文件列表—>/lib/ 、/usr/lib 目录找到库文件后将其载入内存。

export

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/liu/lib

区别

区别来自下面图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6yI72NLU-1633185491320)(assets/1633122480313.png)]

静态库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TZo1mc3W-1633185491321)(assets/1633122496060.png)]

优点:

  • 静态库被打包到应用程序中加载速度快
  • 消耗系统资源,浪费内存
  • 发布程序无需提供静态库,移植方便
  • 更新,部署,发布麻烦

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QosF6AjJ-1633185491322)(assets/1633122630435.png)]

动态库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYcgw7jm-1633185491322)(assets/1633122706444.png)]

优点:

  • 可以实现进程间资源共享
  • 加载速度比静态库慢
  • 更新,部署简单
  • 发布程序需要提供动态库
  • 可以控制何时加载动态库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ya1QMc9e-1633185491323)(assets/1633122782044.png)]

6、文件IO

6.1、标准C库函数(IO函数)有缓存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EumrpgYn-1633185491324)(assets/1633122868255.png)]

6.2、标准C库IO函数和Linux系统IO函数的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AfLlg8MT-1633185491324)(assets/1633122951698.png)]

说明IO(输入输出函数):是针对内存而言的

从内存到磁盘是输出函数;从磁盘到内存是输入函数。

6.3、虚拟地址空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jSjhfbTs-1633185491325)(assets/1633123031441.png)]

6.4 文件描述符

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W2eRLjOh-1633185491325)(assets/1633123048299.png)]

6.5、Linux系统IO函数(Linux系统api一般也称为系统调用)

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int close(int fd);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
perro("aaa"); "aaa":XXXX

stat 结构体
struct stat {
    dev_t st_dev; // 文件的设备编号
    ino_t st_ino; // 节点
    mode_t st_mode; // 文件的类型和存取的权限
    nlink_t st_nlink; // 连到该文件的硬连接数目
    uid_t st_uid; // 用户ID
    gid_t st_gid; // 组ID
    dev_t st_rdev; // 设备文件的设备编号
    off_t st_size; // 文件字节数(文件大小)
    blksize_t st_blksize; // 块大小
    blkcnt_t st_blocks; // 块数
    time_t st_atime; // 最后一次访问时间
    time_t st_mtime; // 最后一次修改时间
    time_t st_ctime; // 最后一次改变时间(指属性)
};


st_mode变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLPfrL9d-1633185491326)(assets/1633123204046.png)]

6.6、文件属性操作函数

int access(const char *pathname, int mode);
int chmod(const char *filename, int mode);
int chown(const char *path, uid_t owner, gid_t group);
int truncate(const char *path, off_t length);

6.7、目录操作函数

int rename(const char *oldpath, const char *newpath);
int chdir(const char *path);
char *getcwd(char *buf, size_t size);
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);

6.8 、目录遍历函数

DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);

6.9、dirent结构体和d_type

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 - 未知

6.10、dup、dup2函数

// 复制文件描述符
int dup(int oldfd);
// 重定向文件描述符
int dup2(int oldfd, int newfd);

6.11、fcntl函数(重点)

// 复制文件描述符、设置/获取文件的状态标志
int fcntl(int fd, int cmd, ... /* arg */ );

多进程

1、进程控制块(PCB)

为了管理进程,内核必须对每个进程所做的事情进行清楚的描述,内核为每个进程分配一个PCB,维护进程相关信息,Linux内核进程块就是task_struct结构体。

在linux-headers-xxx/include/linux/sched.h文件中可以查看struct task_struct 结构体定义。

里面内部成员有很多,我们只需要掌握一部分即可。

  • 进程id:系统中每个进程有唯一的 id,用 pid_t 类型表示,其实就是一个非负整数
  • 进程的状态:有就绪、运行、挂起、停止等状态
  • 进程切换时需要保存和恢复的一些CPU寄存器
  • 描述虚拟地址空间的信息
  • 描述控制终端的信息
  • 当前工作目录(Current Working Directory)
  • umask 掩码 文件描述符表,包含很多指向 file 结构体的指针
  • 信号相关的信息
  • 用户 id 和组 id
  • 会话(Session)和进程组
  • 进程可以使用的资源上限(Resource Limit)

2、进程状态

三态模型和五态模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSS9nFAJ-1633185491326)(assets/1633123841561.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uuHQpwMR-1633185491327)(assets/1633123847860.png)]

3、进程的相关指令

查看进程 ps aux / ajx / ef

a:显示终端上的所有进程,包括其他用户的进程

u:显示进程的详细信息

x:显示没有控制终端的进程

j:列出与作业控制相关的信息

STAT参数意义:

D不可中断 Uninterruptible(usually IO)
R正在运行,或在队列中的进程
S处于休眠状态
T停止或被追踪
Z僵尸进程
W进入内存交换(从内核2.6开始无效)
X死掉的进程
<高优先级
N低优先级
s包含子进程
+位于前台的进程组

实时显示进程动态 TOP命令

4、进程的创建

系统允许一个进程创建一个新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
  • 返回值:
    • 成功: 子进程中返回0,父进程中返回子进程ID
    • 失败: 返回-1

失败的主要原因:

  • 当前系统的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGIN
  • 系统内存不足,这时errno的值被设置为ENOMEN

5、父子进程虚拟地址空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hcWRxZUp-1633185491327)(assets/1633124856219.png)]

准确来说,Linux的fork使用是通过写时复制(copy-on-write)实现。写时复制是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候复制空间,也就是说,资源复制是在需要写入的时候才会进行,在此之前,只有以读的方式共享。

6、exec函数族介绍

exec 函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就 是在调用进程内部执行一个可执行文件。

exec 函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被 新的内容取代,只留下进程 ID 等一些表面上的信息仍保持原样,颇有些神似“三十六计”中的“金蝉脱 壳”。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回 -1,从原程序的调 用点接着往下执行。

int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const
envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
int execve(const char *filename, char *const argv[], char *const envp[]);

7、进程控制,进程分类

进程退出

#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KD07zbTy-1633185491328)(assets/1633125366698.png)]

孤儿进程(没有危害)

父进程运行结束,但子进程还在运行,这样的子进程就称为孤儿进程。(Orphan)

每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它 的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和 政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

僵尸进程(有危害)

每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉,需要 父进程去释放。

子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵死进程。 (Zombie)。

僵尸进程不能被 kill -9 杀死,这样就会导致一个问题,如果父进程不调用 wait() 或 waitpid() 的话,那么 保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大 量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害, 应当避免.

进程回收

在每个进程退出的时候,内核释放该进程所有资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息,这些信息主要指进程控制块(PCB)的信息(包括进程号、退出状态、运行时间等)

父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。

wait 和 waitpid 功能一样

区别:

wait: 函数会阻塞

waitpid:可以设置不阻塞,还可以指定等待哪个子进程结束。

注意:一次wait或waitpid 调用只能清理一个子进程,清理多个子进程应使用循环。

退出信息相关宏函数

WIFEXITED(status)0,进程正常退出
WEXITSTATUS(status) 如果上宏为真,获取进程退出的状态(exit的参数)
WIFSIGNALED(status)0,进程异常终止
WTERMSIG(status) 如果上宏为真,获取使进程终止的信号编号
WIFSTOPPED(status)0,进程处于暂停状态
WSTOPSIG(status) 如果上宏为真,获取使进程暂停的信号的编号
WIFCONTINUED(status)0,进程暂停后已经继续运行

8、进程间通信

在系统中,不同的进程需要进行信息的交互和状态的传递,因此需要进程间的通信(IPC: inter process communication)

进程间通信的目的:

  • 数据传输
  • 通知事件
  • 资源共享
  • 进程控制

Linux进程间通信的方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z4oGKtGW-1633185491328)(assets/1633148617938.png)]

匿名管道

也叫无名管道,是Unix系统最古老的进程间通信方式。所有Unix都支持。

例如:统计一个目录中文件的数目命令:ls | wc -l, 为了执行命令,shell 创建了两个进程来分别执行ls 和wc。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gFEhSUXu-1633185491329)(assets/1633148744951.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-paVHeyvm-1633185491329)(assets/1633148764952.png)]

管道的特点:

管道就是一个在内核中维护的缓冲器,这个缓冲器的存储能力是有限的,不同系统的大小不一定同。

管道拥有文件的特质:读操作,写操作,匿名管道没有文件实体,有名管道有文件实体,但不存储数据,可以按照操作文件的方式对管道进行操作。

一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念

通过管道的传递的数据是顺序的,从管道中读取的字节顺序和它们被写入管道的顺序是完全一样的

在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。

从管道中读数据是一次性操作,数据一旦被读走,他就从管道中被抛弃,释放空间以便写更多数据,在管道中无法使用lseek()来随机访问数据

匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1kRgfeJj-1633185491330)(assets/1633149847235.png)]

匿名管道的使用
// 创建匿名管道
#include <unistd.h>
int pipe(int pipefd[2]);

// 查看管道缓冲大小命令
ulimit –a

// 查看管道缓冲大小函数
#include <unistd.h>
long fpathconf(int fd, int name);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-otuZL2Pp-1633185491330)(assets/1633150033752.png)]

有名管道

1、匿名管道,由于没有名字,只能用于亲缘关系的进程间通信,为了克服缺点,提出了有名管道,也叫FIFO文件。

2、有名管道(FIFO):不同于匿名管道之处在于它提供了一个路径名与之关联。

3、一旦打开了FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的IO系统调用(read,write,close)。与管道一样,FIFO也有一个读端一个写端,读出和写入的顺序是一样的。

4、有名管道(FIFO)和匿名管道(pipe)有一个是相同的,还有是不同的

  • FIFO在文件系统中作为一个特殊文件存在,但FIFO中的内容是存放在内存中
  • 当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中供以后使用
  • FIFO有名字,不相关的进程可以通过打开有名管道进行通信
使用
// 通过命令创建有名管道
mkfifo 名字

// 通过函数创建有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件IO函数都可用于fifo。如close、read、write、unlink等。

FIFO 严格遵循先进先出,不支持lseek操作。

内存映射

内存映射是将磁盘文件的数据映射到内存,用户通过修改内存就能达到修改磁盘文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XuOq1Zc8-1633185491331)(assets/1633150839320.png)]

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t
offset);
int munmap(void *addr, size_t length);

信号

使用信号两个主要目的是:

  • 让进程知道已经发生了一个特定的事情
  • 强迫进程执行它自己代码中的信号处理程序

信号的特点:

  • 简单
  • 不能携带大量信息
  • 满足某个特定条件才发送
  • 优先级比较高

查询系统定义的信号列表 kill -l

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fW7sk1Vb-1633185491331)(assets/1633151018955.png)]

说明: 前面31个信号为常规信号,其余的是实时信号。

Linux信号一览表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ObBPy6u-1633185491332)(assets/1633151091959.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7mwYxT0A-1633185491332)(assets/1633151106061.png)]

信号的5钟默认处理动作

查看信号的详细信息: man 7 signal

信号的5种默认处理动作

  • Term 终止进程
  • lgn 当前进程忽略这个信号
  • Core 终止进程,并生成一个Core文件 (在调试的时候指定ulimit core文件大小为非0,编译时加上 -g,即能生成core文件,然后用gdb生成堆栈错误信息)
  • Stop暂停当前进程
  • Cont 继续执行当前被暂停的进程

信号的几种状态:产生、未决、递达

SIGKILL 和 SIGSTOP信号不能被捕捉、阻塞或者忽略,只能执行默认动作。

信号相关函数
int kill(pid_t pid, int sig);
int raise(int sig);
void abort(void);
unsigned int alarm(unsigned int seconds);
int setitimer(int which, const struct itimerval *new_val, struct itimerval
*old_value);
信号捕捉函数
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signum, const struct sigaction *act, struct sigaction
*oldact);
信号集

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctXVOuF8-1633185491333)(assets/1633151440381.png)]

信号集相关操作函数
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
内核实现信号捕捉的过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rz51xhhQ-1633185491333)(assets/1633151552802.png)]

SIGCHILD 信号

SIGCHILD 信号产生的条件

  • 子进程终止
  • 子进程接收到SIGSTOP信号停止时
  • 子进程处在停止态,接收到SIGCONT后唤醒时

以上三种条件都会给父进程发送SIGCHILD信号,父进程默认会忽略该信号

共享内存

共享内存允许两个或者多个进程共享物理内存的同一块区域。

由于一个共享内存端会称为一个进程用户空间的一部分,因此这种IPC机制无需内核介入。

使用步骤
  • 调用 shmget() 创建一个新共享内存段或取得一个既有共享内存段的标识符(即由其他进程创建的 共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。
  • 使用 shmat() 来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分。
  • 此刻在程序中可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由 shmat()
  • 调用返回的 addr 值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针。
  • 调用 shmdt() 来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存了。这一步是可选的,并且在进程终止时会自动完成这一步。
  • 调用 shmctl() 来删除共享内存段。只有当当前所有附加内存段的进程都与之分离之后内存段才会销毁。只有一个进程需要执行这一步。
相关函数
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
key_t ftok(const char *pathname, int proj_id);
共享内存的相关命令

ipcs 用法和 ipcrm用法

  • 自行某度

线程

(进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位)

与进程类似,线程是允许引用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享一份全局内存区域,其中包含初始化数据段,未初始化数据段,以及堆内存段。

进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。

线程是轻量级的进程(LWP:light weight process)。

查看指定进程的LWP号:ps -Lf pid

进程线程的区别

进程间的信息难以共享。由于除去只读代码段外, 父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。

通过fork 创建进程的代价相对线程来说代价比较高,即便是写时复制技术,仍然需要复制诸如内存页表和文件描述符之类的多种进程属性,这意味这fork 调用在时间上的开销不菲。

线程之间能够方便、快速地共享信息,只需要将数据复制到共享(全局或堆)变量即可。

创建线程比创建进程通常要快10倍甚至更多,线程间是共享地址空间的,无需采用写时复制技术复制内存,也不需要复制页表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fAuTePTO-1633185491334)(assets/1633156826465.png)]

线程之间共享和非共享资源

共享资源

  • 进程ID和父进程ID
  • 进程组ID和回话ID
  • 用户ID和用户组ID
  • 文件描述符
  • 信号处理
  • 文件系统的相关信息:文件权限掩码(umask)、当前工作目录
  • 虚拟地址空间(除栈 .text)

非共享资源

  • 线程ID
  • 信号掩码
  • 线程特有数据
  • errno变量
  • 实时调度策略和优先级
  • 栈、本地变量和函数的调用链接信息

线程操作函数

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine) (void *), void *arg);
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2);
void pthread_exit(void *retval);    // 退出线程,在主函数都可以使用,不对子线程产生影响,如果主线程直接返回就是调用的是exit(0),这个会对子线程造成影响。
int pthread_join(pthread_t thread, void **retval);
int pthread_detach(pthread_t thread);
int pthread_cancel(pthread_t thread);


pthread_detach()
主线程与子线程分离,子线程结束后,资源自动回收。

pthread_join()
是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源。


线程属性相关
线程属性类型 pthread_attr_t
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

线程同步

简介

线程的主要优势在于,能够通过全局变量来共享信息。不过,这种便捷的共享是有代价的;必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量

临界区

临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是不能打断。

线程同步

线程同步即当有一个线程在对内存操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。

互斥量

mutex

互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。

一般的线程在访问同一资源将采用下面方式:

  • 针对共享资源锁定互斥量

  • 访问共享资源

  • 对互斥量解锁

如果其他线程想执行该代码将会阻塞。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxQUXk4x-1633185491334)(assets/1633157563596.png)]

互斥量相关操作函数
互斥量的类型 pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const
pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
死锁

一个线程需要同时访问两个或者多个不同的共享资源,而每个资源由不同的互斥量管理。当超过一个线程加锁同一组互斥量时,就可能发生死锁。

两个或两个以上的进程在执行过程中,因争夺资源而造成一种相互等待的现象,若无外力,将会一直死在这里。

发生死锁的几个场景:

  • 忘记释放锁
  • 重复加锁
  • 多线程多锁,抢夺资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cezTxa4g-1633185491335)(assets/1633157994030.png)]

读写锁

前提:

当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形, 当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由 于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题

在对数据库的读写操作中,更多的是读操作,写操作比较少。为了满足允许多个读出,线程提供了读写锁实现。

特点:

  • 如果有其他线程读数据,则允许其他线程执行操作,但不允许写操作
  • 如果有其他线程写数据,则其他线程都不允许读,写操作
  • 写时独占的,写优先级高。
相关的函数
读写锁的类型 pthread_rwlock_t
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const
pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
生产者消费者模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YiAglfVl-1633185491335)(assets/1633158386779.png)]

条件变量
条件变量的类型 pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t
*restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict
mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t
*restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
信号量
信号量的类型 sem_t
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem, int *sval);

网络编程

1、MAC

2、IP地址

3、端口

4、网络模型

TCP/IP四层模型

5、协议

常见协议
UDP协议
TCP协议
IP协议
以太网协议
ARP协议
封装
分用

6、网络通信过程

7、socket介绍

8、字节序

9、socket地址

10、IP地址转换

11、TCP通信流程(相关函数流程)

// TCP 和 UDP -> 传输层协议

UDP: 用户数据报协议,面向无连接,可以单播,多播,广播,面向数据报,不可靠

TCP:传输控制协议,面向连接,可靠的,基于字节流,仅支持单播

UDPTCP
是否创建连接无连接面向连接
是否可靠
连接的对象1对1,1对多,多对1,多对多支持1对1
传输的方式面向数据报面向字节流
首部开销8字节最少20字节
使用场景实时应用(视频会议,直播)可靠性高应用(文件传输)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lEQQuian-1633185491336)(assets/1633159452811.png)]

// TCP 通信的流程

// 服务端

  1. 创建一个用于监听的套接字
    • 监听:监听有客户端的连接
    • 套接字:这个套接字其实就是一个文件描述符
  2. 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)
    • 客户端连接服务器的时候使用的就是这个IP和端口
  3. 设置监听,监听fd开始工作
  4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个客户端通信的套接字(fd)
  5. 通信
    • 接受数据、 发送数据
  6. 通信结束,断开连接

// 客户端

  1. 创建一个用于通信的套接字(fd)
  2. 连接服务器,需要指定连接的服务器的IP和端口
  3. 连接成功了,客户端可以直接和服务器通信
    • 接受数据、发送数据
  4. 通信结束,断开连接

12、套接字函数

13、TCP三次握手

14、TCP滑动窗口

15、TCP四次挥手

16、TCP通信并发

17、TCP状态转换

18、端口复用

19、IO多路复用(IO多路转换)

select
poll
epoll

| ------------------------ |
| 是否创建连接 | 无连接 | 面向连接 |
| 是否可靠 | 否 | 是 |
| 连接的对象 | 1对1,1对多,多对1,多对多 | 支持1对1 |
| 传输的方式 | 面向数据报 | 面向字节流 |
| 首部开销 | 8字节 | 最少20字节 |
| 使用场景 | 实时应用(视频会议,直播) | 可靠性高应用(文件传输) |

[外链图片转存中…(img-lEQQuian-1633185491336)]

// TCP 通信的流程

// 服务端

  1. 创建一个用于监听的套接字
    • 监听:监听有客户端的连接
    • 套接字:这个套接字其实就是一个文件描述符
  2. 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)
    • 客户端连接服务器的时候使用的就是这个IP和端口
  3. 设置监听,监听fd开始工作
  4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个客户端通信的套接字(fd)
  5. 通信
    • 接受数据、 发送数据
  6. 通信结束,断开连接

// 客户端

  1. 创建一个用于通信的套接字(fd)
  2. 连接服务器,需要指定连接的服务器的IP和端口
  3. 连接成功了,客户端可以直接和服务器通信
    • 接受数据、发送数据
  4. 通信结束,断开连接

12、套接字函数

13、TCP三次握手

14、TCP滑动窗口

15、TCP四次挥手

16、TCP通信并发

17、TCP状态转换

18、端口复用

19、IO多路复用(IO多路转换)

select
poll
epoll
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值