Linux多进程、多线程、网络编程常见问题
入门
1、GCC的工作流程?
工作流程:
预处理、编译、汇编、链接
具体流程如下:
2、gcc常用的参数选项
3、Makefile介绍
3.1、Makefile文件命令和规则
-
文件命名:makefile 和Makefile -
一个Makefile文件中可以有一个或者多个规则 目标 ... : 依赖
命令(shell命令)
...
目标:最终要生成的文件(伪目标除外) 依赖:生成目标所需要的文件或是目标 命令:通过执行命令对依赖操作生成目标(命令前必须Tab缩进) Makefile中的其他规则一般是为第一条规则服务的
3.2、基本原理
- 命令在执行之前,需要先检查规则中的依赖是否存在
- 如果存在,执行命令
- 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
- 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
- 如果依赖的时间比目标的时间晚,需要重新生成目标
- 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
3.3、变量
-
自定义变量 var=liu
$var
-
预定义变量
- 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;
gid_t st_gid;
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
{
ino_t d_ino;
off_t d_off;
unsigned short int d_reclen;
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, ... );
多进程
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, ...);
int execlp(const char *file, const char *arg, ... );
int execle(const char *path, const char *arg, ...);
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);
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:传输控制协议,面向连接,可靠的,基于字节流,仅支持单播
| UDP | TCP |
---|
是否创建连接 | 无连接 | 面向连接 | 是否可靠 | 否 | 是 | 连接的对象 | 1对1,1对多,多对1,多对多 | 支持1对1 | 传输的方式 | 面向数据报 | 面向字节流 | 首部开销 | 8字节 | 最少20字节 | 使用场景 | 实时应用(视频会议,直播) | 可靠性高应用(文件传输) |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lEQQuian-1633185491336)(assets/1633159452811.png)]
// TCP 通信的流程
// 服务端
- 创建一个用于监听的套接字
- 监听:监听有客户端的连接
- 套接字:这个套接字其实就是一个文件描述符
- 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)
- 设置监听,监听fd开始工作
- 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个客户端通信的套接字(fd)
- 通信
- 通信结束,断开连接
// 客户端
- 创建一个用于通信的套接字(fd)
- 连接服务器,需要指定连接的服务器的IP和端口
- 连接成功了,客户端可以直接和服务器通信
- 通信结束,断开连接
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 通信的流程
// 服务端
- 创建一个用于监听的套接字
- 监听:监听有客户端的连接
- 套接字:这个套接字其实就是一个文件描述符
- 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)
- 设置监听,监听fd开始工作
- 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个客户端通信的套接字(fd)
- 通信
- 通信结束,断开连接
// 客户端
- 创建一个用于通信的套接字(fd)
- 连接服务器,需要指定连接的服务器的IP和端口
- 连接成功了,客户端可以直接和服务器通信
- 通信结束,断开连接
12、套接字函数
13、TCP三次握手
14、TCP滑动窗口
15、TCP四次挥手
16、TCP通信并发
17、TCP状态转换
18、端口复用
19、IO多路复用(IO多路转换)
select
poll
epoll
|