Linux高并发服务器-Linux多进程开发
进程概述
进程是正在运行的程序的实例。进程是操作系统动态执行的基本单元
进程控制块PCB
进程控制块 PCB, linux内核是task_struck结构体,在 / usr / src / linux-headers-xxx / include / linux / sched.h 文件中可以查看
进程状态转换
进程相关命令
查看进程
ps aux / ajx
a: 显示终端的所有进程,包括其他用户的进程
u: 显示进程的详细信息
x: 显示没有控制终端的进程
j: 列出与作业控制相关的信息
进程STAT参数 | 意义 |
---|---|
D | 不可中断 |
R | 正在运行,或在队列中 |
S | 休眠 |
s | 包含子进程 |
T | 停止或被追踪 |
Z | 僵尸进程 |
W | 进入内存交换 |
X | 死掉的进程 |
< | 高优先级 |
N | 低优先级 |
+ | 位于前台的进程组 |
实时显示进程动态
top 加上 -d 可指定显示信息更新的时间间隔,top执行后,按键对显示结果排序
M 按内存排序
P CPU占有率
T 进程运行时间
U 用户名筛选
K 输入指定PID杀死进程
杀死进程
kill [-signal] pid
kill -l 列出所有信号
kill -SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名杀死进程
进程号和相关函数
每个进程由进程号标识,类型pid_t(整型),范围 0 ~ 32767
任何进程(除了init进程)都有父进程(PPID)
进程组是一个或多个进程的集合,进程组号(PGID)
pid_t getpid(void);
pid_t getppid(void);
pid_t getpgid(pid_t pid);
进程创建
#include <sys/types.h>
#include <unistd.h>
//创建一个子进程
pid_t fork(void);
返回值:
-成功:子进程返回0,父进程返回创建的子进程的ID
-失败;在父进程中返回-1
失败原因:
-系统进程数达到系统上限,errno的值设置位EAGAIN
-系统内存不足,errno的值设置位ENOMEM
父子进程
linux的 fork( ) 使用通过 写时拷贝 实现,写时拷贝 是一种可以推迟甚至避免拷贝数据的技术。
内核此时并不复制整个进程的地址空间,父子进程共享一个地址空间。
只用在写入时才复制地址空间,资源的复制在需要写入时才进行,此前只有只读方式共享。
注意: fork ( ) 后父子进程共享文件
区别
- fork ( ) 返回值不同
- pcb 中的数据不同 pid, ppid, 信号集
共同点
- 子进程创建后,没执行写数据操作时
- 用户区的数据
- 文件描述符表
- 父子进程对变量,未修改数据时共享。
GDB 多进程调试
GDB默认跟踪父进程
设置调试父进程或子进程
set follow-fork-mode [ parent | child ]
设置调试模式
set detach-on-fork [ on | off ]
默认为on, 表示调试当前进程时, 其他进程继续运行, 为off, 调试当前进程时,其他进程被GDB挂起。
查看调试的进程
info inferiors
切换当前调试的进程
inferior id
使进程脱离 GDB 调试
detach inferiors id
exec函数族
exec函数族的作用是根据指定的文件名找到可执行文件。即在调用进程内部执行一个可执行文件。
exec函数族执行成功不会返回,调用进程的实体背心的内容取代,只有调用失败才返回-1
#include <unistd.h>
extern char **environ;
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[]);
l(list) 参数地址列表,以空指针结尾
v(vector) 存有各参数地址的指针数组的地址
p(path) 按PATH环境变量指定目录搜索
e(environment)为可执行程序添加新的环境变量
参数:
-arg:执行可执行文件的所需的参数列表
-第一个参数:一般是文件名
-参数最后以NULL结束
返回值:调用失败返回-1,设置errno; 成功没有返回值。
进程退出
孤儿进程
父进程运行结束,子进程还在运行。每当出现一个孤儿进程,内核把孤儿进程的父进程设置为init,init会循环 wait() 。
守护进程
进程组、会话、控制终端之间的关系
进程组、会话操作
守护进程创建步骤
僵尸进程
每个进程结束,都会释放自己地址空间的用户去数据,内核区的PCB没办法自己释放掉,需要父进程去释放。
进程终止时,父进程不调用wait(),waitpid() 回收,子进程残留资源存放在内核中,变成僵尸进程。
僵尸进程不能被kill -9杀死
wait () , waitpid()
wait() 会阻塞, waitpid() 可以设置不阻塞,可以指定等待哪个子进程结束。
一次wait() 和waitpid() 只能清理一个子进程
#include <sys/types.h>
#include <sys/wait.h>
//等待任意一个子进程结束,回收子进程
pid_t wait(int *wstatus);
参数:
-wstatus:进程退出时的状态信息,传入一个int类型的地址,传出参数
返回值:
-成功:返回被回收子进程的id
-失败;-1(所有子进程结束,调用函数失败)
注意:调用wait函数的进程会被挂起(阻塞),直到他的一个子进程退出或者收到一个不能被忽略的信号
//回收指定进程号的子进程,可以设置是否阻塞
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数:
-pid:
>0 子进程id
=0 回收当前进程组的所有子进程
-1 回收所有子进程->wait()
<-1 回收某个进程组的组id的进程,回收直径进程组的子进程
-option:
0: 阻塞
WNOHANG:非阻塞
返回值:
>0: 子进程的id
=0: option=WNOHANG,表示还有子进程或者
=-1:错误,或没有子进程了
进程通信
不同进程之间的资源是独立,不能在一个进程直接访问另一个进程的资源。
进程间通信(IPC):
- 数据传输:
- 通知事件:
- 资源共享:
- 进程控制:
匿名管道(管道)
管道的特点
匿名管道的使用
#include <unistd.h>
//创建一个匿名管道,用于进程间的通信
int pipe(int pipefd[2]);
参数:数组是一个传出参数
pipefd[0]:管道的读端
pipefd[1]:管道的写端
返回值:成功- 0 失败- -1
管道默认阻塞
注意:只能用于公共祖先的进程
//查看管道缓冲的大小
ulimit -a
#include <unistd.h>
//获取管道的大小
long fpathconf(int fd, int name);
参数:
name:
_PC_PIPE_BUF
设置管道非阻塞
int flag = fcntl(fd, F_GETFL);
flag |= O_NONBLOCK ;
int ret = fcntl(fd, F_SETFL, flag)
有名管道(FIFO)
FIFO作为文件系统的一个特殊文件,但其中的内容放在内存中的,FIFO退出后,FIFO文件将继续保存在文件系统中方便以后使用。
有名管道的使用
//命令创建
mkfifo 名字
//函数创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
mode:同open
返回:成功 - 0 失败 - -1
内存映射
系统调用
#include <sys/mman.h>
//将一个文件或设备的数据映射到地址空间
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
参数:
-void * adder: NULL,内核指定
-size_t length: 要映射的数据的长度(不能为0)
-prot: 对申请内存的操作权限
-PORT_EXEC:可执行权限
-PROT_READ:读权限
-PORT_WRITE:写权限
-PORT_NONE:无权限
要映射文件,必须要有读权限 PROT_R、 PROT_READ|PORT_WRITE
-flags:
-MAP_SHARED:映射区的数据自动和磁盘文件同步,进程间通信,必须设置
-MAP_PRIVATE:不同步,映射区改变,原来文件不变
-MAP_ANONYMOUS:匿名映射(不需要文件实体,不能进行没有关系的进程通信)
-fd:
注意:通过open的磁盘文件得到,文件大小不能为0,open的权限必须不小于prot
-off_t offset:偏移量,一般不用,必须指定4K的整数倍,0表示不偏移
返回值:返回创建的内存的首地址,失败返回MAP_FAILED, (void*) -1
//释放内存映射
int munmap(void *addr, size_t length);
参数:
-addr: 释放内存的首地址
-length:要释放的内存的大小,要求和mmap中的一样。
相关问题
- 内存映射的进程间通信是非阻塞的
- 对mmap返回值(void*)ptr ++ ,可以操作,++后释放出错,保存返回的地址
- prot权限不能大于open,建议保持一致
- 偏移量必须为4k整数倍
- mmap调用失败:
- 参数length=0;
- prot
- 只指定写权限
- 与open权限不一致
- open一个新文件创建映射区,大小不能为0,用lseek()\truncate()扩展
- mmap后关闭文件描述符,映射区还存在,
- ptr越界操作是非法内存,产生段错误
信号
信号概述
常规信号
信号代号 | 信号名称 | 说 明 |
---|---|---|
1 | SIGHUP | 该信号让进程立即关闭,然后重新读取配置文件之后重启 |
2 | SIGINT | 程序中止信号,用于中止前台进程。相当于输出 Ctrl+C 快捷键 |
3 | SIGQUIT | 和SIGINT类似, 但由QUIT字符(通常是Ctrl-/)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。 |
4 | SIGILL | 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。 |
5 | SIGTRAP | 由断点指令或其它trap指令产生. 由debugger使用。 |
6 | SIGABRT | 调用abort函数生成的信号。 |
7 | SIGBUS | 非法地址, 包括内存地址对齐(alignment)出错。 |
8 | SIGFPE | 在发生致命的算术运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为 0 等其他所有的算术运算错误 |
9 | SIGKILL | 用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。般用于强制中止进程 |
10 | SIGUSR1 | 留给用户使用 |
11 | SIGSEGV | 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据. |
12 | SIGUSR2 | 留给用户使用 |
13 | SIGPIPE | 管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。 |
14 | SIGALRM | 时钟定时信号,计算的是实际的时间或时钟时间。alarm 函数使用该信号 |
15 | SIGTERM | 正常结束进程的信号,kill 命令的默认信号。如果进程已经发生了问题,那么这 个信号是无法正常中止进程的,这时我们才会尝试 SIGKILL 信号,也就是信号 9 |
17 | SIGCHLD | 子进程结束时, 父进程会收到这个信号。 |
18 | SIGCONT | 该信号可以让暂停的进程恢复执行。本信号不能被阻断 |
19 | SIGSTOP | 该信号可以暂停前台进程,相当于输入 Ctrl+Z 快捷键。本信号不能被阻断 |
20 | SIGTSTP | 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号 |
21 | SIGTTIN | 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行. |
22 | SIGTTOU | 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到. |
23 | SIGURG | 有"紧急"数据或out-of-band数据到达socket时产生. |
24 | SIGXCPU | 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。 |
25 | SIGXFSZ | 当进程企图扩大文件以至于超过文件大小资源限制。 |
26 | SIGVTALRM | 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间. |
27 | SIGPROF | 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间. |
28 | SIGWINCH | 窗口大小改变时发出. |
29 | SIGIO | 文件描述符准备就绪, 可以开始进行输入/输出操作. |
30 | SIGPWR | Power failure |
31 | SIGSYS | 非法的系统调用 |
信号相关函数
kill(), raise() , abort()
#include <sys/types.h>
#include <signal.h>
//给任何进程组或进程的pid,发送某个信号sig
int kill(pid_t pid, int sig);
参数:
-pid:
> 0: 信号发送给指定进程
= 0: 将信号发送给当前进程组
= -1: 将信号发送给每一个有权限接受这个信号的进程
< -1: 这个pid=某个进程组的ID取反
#include <signal.h>
//给当前进程发送信号
int raise(int sig);
返回值:成功 -0 失败 非0
#include <stdlib.h>
//发送SIGABRT信号给当前进程 ,杀死当前进程
void abort(void);
alarm()
#include <unistd.h>
//设置定时器(闹钟),函数调用,开始倒计时,倒计时为0,函数给当前进程发送一个信号:SIGALARM
unsigned int alarm(unsigned int seconds);
参数:
seconds:单位秒,参数为0,定时器无效(不进行倒计时),取消一个定时器:alarm(0).
返回值:
-之前没有定时器:返回0
-之前有定时器,返回之前定时器剩余时间
SIGALARM:默认终止当前进程,每一个进程有且只有唯一的定时器。
alarm(10); ->返回0
过1s;
alarm(5); ->返回9
alarm()函数不阻塞。
实际时间 = 内核时间 + 用户时间 + 消耗的时间
进行文件IO操作比较消耗时间。
定时器,与进程的状态无关(自然定时法),无论进程什么状态,alarm都会计时
setitimer()
#include <sys/time.h>
//设置定时器,精度us,可以实现周期性定时
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
参数:
-which:定时器以什么时间计时
-ITIMER_REAL:真实时间,时间到达,发送SIGALRM 常用
-ITIMER_VIRTUAL:用户时间,时间到达,发送 SIGVTALRM
-ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计算,时间到发送SIGPROF
-new_value:设置定时器的属性
struct itimerval { //定时器的结构体
struct timeval it_interval; //每个阶段的时间,间隔时间
struct timeval it_value; //延迟多长时间执行定时器
};
struct timeval { //时间的结构体
time_t tv_sec; //秒数
suseconds_t tv_usec; //微秒
};
-old_value:记录上一次定时的时间参数,一般为NULL
返回值:成功 -0 失败 -1
setitimer()非阻塞
信号捕捉
内核实现信号捕捉的过程
sighandler_t signal ()
//设置某个信号的捕捉行为
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:
-signum:要捕捉的信号
-handle:捕捉到信号如果处理
-SIG_IGN:忽略信号
-SIG_DFL:使用信号默认的行为
-回调函数:内核调用,程序员只负责写,捕捉到信号后如何处理信号。
返回值:
成功,返回上一次注册的信号处理函数的地址,第一次调用返回NULL
失败,返回SIG_ERR,设置错误信号。
SIGKILL,SIHSTOP不能被捕捉,不能被忽略
信号集及相关函数
#include <signal.h>
以下信号集相关函数对自定义的信号集进行操作
//清空信号集数据
int sigemptyset(sigset_t *set);
//信号集标志位设置为1
int sigfillset(sigset_t *set);
//添加信号集的某一个信号
int sigaddset(sigset_t *set, int signum);
//删除信号集的某一个信号
int sigdelset(sigset_t *set, int signum);
返回值:成功 0 失败 -1
//判断某个信号是否在信号集
int sigismember(const sigset_t *set, int signum);
返回值:1 阻塞 0 不阻塞 -1 错误
sigprocmask(), sigpending()
#include <signal.h>
//将自定义信号集的数据设置到内核(设置阻塞,解除阻塞,替换)
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
-参数:
-how:如何对内核阻塞信号集处理
SIG_BLOCK: mask | set set为1的位mask的位 置1
SIG_UNBLOCK: mask&=~set set为1的位mask的位 置0
SIG_SETMASK: 覆盖mask的值
-set: 已初始化好的用户自定义信号集
-oldeset: 保存设置之前的内核的信号集的状态,可能为NULL
返回值:
成功 -0
失败 -1 设置错误号EFAULT、EINVAL
//获取内核中的未决信号集
int sigpending(sigset_t *set);
参数:set, 传出参数
sigaction ()
#include <signal.h>
//检查或改变信号的处理,信号捕捉
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数:
-act:捕捉信号后的处理动作
-oldact:上一次对信号捕捉的相关设置,一般不用,NULL
返回值:成功0 失败-1
struct sigaction {
//函数指针,指向的函数是信号捕捉后的处理函数,sa_hander
void (*sa_handler)(int);
//不常用,sa_sigaction
void (*sa_sigaction)(int, siginfo_t *, void *);
//临时阻塞信号集,临时阻塞某些信号
sigset_t sa_mask;
//使用哪一个信号对捕捉到的信号处理
//可以是0,表示使用sa_hander,也可以是SA_SIGINFO 表示使用sa_sigaction
int sa_flags;
//被废弃,NULL
void (*sa_restorer)(void);
};
SIGCHILD信号
用于解决僵尸进程
注意,提前设置好阻塞信号集,避免子进程很快结束,父进程还没注册完信号捕捉
共享内存
共享内存使用步骤
共享内存函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
-key: key_t整形,16进制,非0
-size: 共享内存大小
-shmflg:属性
-访问权限
-附加属性:
-创建:IPC_CREAT
-判断内存是否存在:IPC_EXCL,和 IPC_CREAT一起使用
IPC_CREAT|IPC_EXEL|0664
返回值:成功>0 返回共享内存引用的ID 失败 -1 设置错误号
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
-shmid:共享内存ID
-shmaddr:共享内存起始地址,指定NULL,内核指定
-shmflg:
-读:SHM_RDONLY,必须要有读权限
-读写:0
返回值:共享内存首地址
int shmdt(const void *shmaddr); 成功返回0 失败-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
-cmd:操作
-IPC_STAT:获取共享内存当前状态
-IPC_SET:设置共享内存状态
-IPC_RMID:标记共享内存被销毁
-buf:需要设置或获取的共享内存的属性信息
-IPC_STAT:buf存储数据
-IPC_SET:buf中需要初始化数据,设置到内核
-IPC_RMID:没有用,NULL
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
//根据指定的路径名,和int值,生成一个共性内存的key
key_t ftok(const char *pathname, int proj_id);
参数:
-proj_id:系统只会调用其中的1个字节
范围:0-255 一般指定一个字符'a'
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!