Linux高并发服务器-Linux多进程开发

进程概述

进程是正在运行的程序的实例。进程是操作系统动态执行的基本单元

进程控制块PCB

进程控制块 PCB, linux内核是task_struck结构体,在 / usr / src / linux-headers-xxx / include / linux / sched.h 文件中可以查看

进程状态转换

Alt text

进程相关命令

查看进程

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 ( ) 后父子进程共享文件

区别

  1. fork ( ) 返回值不同
  2. pcb 中的数据不同 pid, ppid, 信号集

共同点

  1. 子进程创建后,没执行写数据操作时
    1. 用户区的数据
    2. 文件描述符表
  2. 父子进程对变量,未修改数据时共享。

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; 成功没有返回值。

进程退出

Alt text

孤儿进程

父进程运行结束,子进程还在运行。每当出现一个孤儿进程,内核把孤儿进程的父进程设置为init,init会循环 wait() 。

守护进程

Alt text

进程组、会话、控制终端之间的关系

Alt text

进程组、会话操作

Alt text

守护进程创建步骤

Alt text

僵尸进程

每个进程结束,都会释放自己地址空间的用户去数据,内核区的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:错误,或没有子进程了

Alt text

进程通信

不同进程之间的资源是独立,不能在一个进程直接访问另一个进程的资源。
进程间通信(IPC):

  1. 数据传输:
  2. 通知事件:
  3. 资源共享:
  4. 进程控制:

匿名管道(管道)

管道的特点

Alt text
Alt text
Alt text

匿名管道的使用

#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)

Alt text
FIFO作为文件系统的一个特殊文件,但其中的内容放在内存中的,FIFO退出后,FIFO文件将继续保存在文件系统中方便以后使用。

有名管道的使用

//命令创建
mkfifo 名字
//函数创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
    参数:
        mode:同open
    返回:成功 - 0    失败 - -1

内存映射

Alt text

系统调用

#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中的一样。

相关问题

  1. 内存映射的进程间通信是非阻塞的
  2. 对mmap返回值(void*)ptr ++ ,可以操作,++后释放出错,保存返回的地址
  3. prot权限不能大于open,建议保持一致
  4. 偏移量必须为4k整数倍
  5. mmap调用失败:
    1. 参数length=0;
    2. prot
      1. 只指定写权限
      2. 与open权限不一致
  6. open一个新文件创建映射区,大小不能为0,用lseek()\truncate()扩展
  7. mmap后关闭文件描述符,映射区还存在,
  8. ptr越界操作是非法内存,产生段错误

信号

信号概述

Alt text

Alt text

常规信号

信号代号 信号名称 说 明
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 非法的系统调用

Alt text

信号相关函数

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); ->返回01s;
    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()非阻塞

信号捕捉

内核实现信号捕捉的过程

Alt text

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不能被捕捉,不能被忽略

信号集及相关函数

Alt text

#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信号

Alt text
用于解决僵尸进程
注意,提前设置好阻塞信号集,避免子进程很快结束,父进程还没注册完信号捕捉

共享内存

Alt text

共享内存使用步骤

Alt text

共享内存函数

#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'

共享内存操作命令

Alt text

posted @   玩世不恭xxh  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示