【Linux】多进程编程

news2025/6/20 15:30:03

目录

1. 进程基础知识

2. 查看进程

3. 杀死进程

4. 获取进程标识符

5. 进程创建

6. 进程终止

7. 进程等待

8. 进程程序替换

9. 进程间通信之管道

9.1 匿名管道

9.2 命名管道(FIFO)

10. 进程间通信之共享内存

11. 进程间通信之信号

11.1 Linux信号

11.2 产生信号

11.3 捕捉信号

11.4 信号集

11.4.1 未决信号集和阻塞信号集

11.4.2 信号集函数


1. 进程基础知识

详见

5.1 进程、线程基础知识 | 小林codingicon-default.png?t=N7T8https://xiaolincoding.com/os/4_process/process_base.html#%E8%BF%9B%E7%A8%8B

2. 查看进程

  • ps 命令
ps aux / ajx
# a:显示终端上的所有进程,包括其他用户的进程
# u:显示进程的详细信息
# x:显示没有控制终端的进程
# j:列出与作业控制相关的进程

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

  • top命令(实时显示进程动态)
top         # 默认3s刷新一次
top -d 5    # 5s刷新一次
top -d 8    # 8s刷新一次

在top命令执行后,可以按以下按键对显示的结果进行排序:

  1. M 根据内存使用量排序
  2. P 根据CPU占有率排序
  3. T 根据进程运行时间长短排序
  4. U 根据用户名来筛选进程
  5. K 输入指定的PID杀死进程

3. 杀死进程

kill PID       # 默认信号是15,等价于kill -15 PID,或kill -SIGTERM PID
kill -9 PID    # 强制杀死,等价于kill -SIGKILL PID
killall name   # 根据进程名杀死进程
kill -l        # 列出所有信号

4. 获取进程标识符

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);  // 获取进程ID
pid_t getppid(void); // 获取父进程ID

5. 进程创建

#include <unistd.h>
pid_t fork(void);
// 父进程返回子进程ID,子进程返回0;失败时返回-1并设置errno

fork函数复制当前进程,在内核进程表中创建一个新的进程表项。新的进程表项有很多属性和原进程相同,比如堆指针、栈指针和标志寄存器的值。但也有许多属性被赋予了新的值,比如该进程的PPID被设置成原进程的PID,信号位图被清除(原进程设置的信号处理函数不再对新进程起作用)。

读时共享,写时拷贝:

子进程的代码与父进程完全相同,同时它还会复制父进程的数据(堆数据、栈数据和静态数据)。数据的复制采用的是所谓的写时复制(copy on write),即只有在任一进程(父进程或子进程)对数据执行了写操作时,复制才会发生(先是缺页中断,然后操作系统给子进程分配内存并复制父进程的数据)。即便如此,如果我们在程序中分配了大量内存,那么使用fork时也应当十分谨慎,尽量避免没必要的内存分配和数据复制。

此外,创建子进程后,父进程中打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数加1。不仅如此,父进程的用户根目录、当前工作目录等变量的引用计数均会加1。

#include <unistd.h>
#include <stdio.h>

int gval = 10;
int main()
{
    int lval = 20;
    gval++;
    lval += 5;
    pid_t pid = fork();
    if (pid == 0) // 子进程
    {
        gval += 2;
        lval += 2;
    }
    else          // 父进程
    {
        gval -= 2;
        lval -= 2;
    }
    if (pid == 0) // 子进程
    {
        printf("子进程gval %2d lval %2d\n", gval, lval);
    }
    else          // 父进程
    {
        printf("父进程gval %2d lval %2d\n", gval, lval);
    }
    return 0;
}
// 父进程gval  9 lval 23
// 子进程gval 13 lval 27

6. 进程终止

  • C/C++库函数exit
  • 系统调用函数_exit
  • main函数内部return语句(main函数内部,return n等价于exit(n))
  • Ctrl+C,信号终止
#include <stdlib.h>
void exit(int status);

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

// status    进程退出状态码,0表示正常退出,非0表示异常退出

#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello");
    exit(0);
    return 0;
}

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello");
    _exit(0);
    return 0;
}

7. 进程等待

父进程运行结束,但子进程还在运行,这样的子进程就称为孤儿进程。孤儿进程将被init进程(PID为1)所收养,并由init进程对它们完成状态收集工作。因此,孤儿进程并不会有什么危害。

子进程运行结束,而父进程又没有回收子进程、释放子进程占用的资源,此时子进程将成为一个僵尸进程。僵尸进程不能被kill -9杀死。

僵尸进程的危害:

  • 占用系统资源,造成资源浪费。
  • 占用PID,如果存在大量的僵尸进程,将因为没有可用的PID而导致系统不能产生新的进程。

父进程应该等待子进程运行结束,回收子进程资源,获取子进程退出状态信息,才能避免僵尸进程。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);
// wait函数将阻塞进程,直到该进程的某个子进程运行结束
// 成功时返回运行结束的子进程ID,失败时返回-1
// status    输出型参数,获取子进程退出状态信息,不关心则设置为NULL
// 退出状态信息相关宏函数:
// WIFEXITED(status)     非0,进程正常终止
// WEXITSTATUS(status)   如果上宏为真,获取进程退出状态码
// WIFSIGNALED(status)   非0,进程异常终止
// WTERMSIG(status)      如果上宏为真,获取使进程终止的信号编号
// WIFSTOPPED(status)    非0,进程处于暂停状态
// WSTOPSIG(status)      如果上宏为真,获取使进程暂停的信号的编号
// WIFCONTINUED(status)  非0,进程暂停后已经继续运行

pid_t waitpid(pid_t pid, int* status, int options);
// waitpid只等待由pid参数指定的子进程
// 成功时返回运行结束的子进程ID,失败时返回-1
// pid        目标子进程ID,如果值为-1,则和wait函数相同,即等待任意一个子进程结束
// status     和wait函数相同
// options    可以控制waitpid函数的行为,通常为WNOHANG,此时waitpid调用是非阻塞的
//            如果目标子进程还没有结束或意外终止,则waitpid立即返回0
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    int status;
    pid_t pid = fork(); // 创建子进程1
    if (pid == 0)       // 子进程1
    {
        return 3;       // 子进程1终止
    }
    else                // 父进程
    {
        printf("子进程1的PID %d\n", pid);
        pid = fork();   // 创建子进程2
        if (pid == 0)   // 子进程2
        {
            exit(7);    // 子进程2终止
        }
        else            // 父进程
        {
            printf("子进程2的PID %d\n", pid);
            wait(&status);         // 回收子进程资源,获取子进程退出状态信息
            if (WIFEXITED(status)) // 判断子进程是否正常终止
            {
                printf("子进程退出状态码 %d\n", WEXITSTATUS(status));
            }
            wait(&status);         // 因为之前创建了2个进程,所以再次调用wait函数和宏
            if (WIFEXITED(status))
            {
                printf("子进程退出状态码 %d\n", WEXITSTATUS(status));
            }
            sleep(30); // 为暂停父进程终止而插入的代码,此时可以查看子进程的状态
        }
    }
    return 0;
}
// 子进程1的PID 1721
// 子进程2的PID 1722
// 子进程退出状态码 7
// 子进程退出状态码 3
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    int status;
    pid_t pid = fork(); // 创建子进程
    if (pid == 0)       // 子进程
    {
        sleep(15);      // 子进程延迟15s
        return 24;
    }
    else                // 父进程
    {
        while (!waitpid(-1, &status, WNOHANG)) // 这个waitpid调用是非阻塞的
        {
            sleep(3);
            puts("sleep 3sec.");
        }
        if (WIFEXITED(status))
        {
            printf("子进程退出状态码 %d\n", WEXITSTATUS(status));
        }
    }
    return 0;
}
// sleep 3sec.
// sleep 3sec.
// sleep 3sec.
// sleep 3sec.
// sleep 3sec.
// 子进程退出状态码 24

8. 进程程序替换

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的ID并未改变。

exec函数族:

#include <unistd.h>
// 标准C库中的函数
int execl(const char* path, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* path, const char* arg, ..., 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[]);
// Linux系统调用函数
int execve(const char* filename, char* const argv[], char* const envp[]);
// 成功时不返回,原程序中exec调用之后的代码都不会执行(被替换了);失败时返回-1并设置errno

// 参数:
// path    可执行文件路径,绝对路径、相对路径均可
// file    可执行文件名称,该文件的具体位置在环境变量PATH中搜索
// arg     可执行文件的命令行参数列表,以空指针结尾
// argv    可执行文件的命令行参数数组
// arg和argv都会被传递给可执行文件的main函数
// envp    新进程的环境变量

// 命名:
// l(list)           命令行参数列表
// v(vector)         命令行参数数组
// p(path)           在环境变量PATH中搜索file
// e(environment)    使用新的环境变量

9. 进程间通信之管道

进程间通信(Inter Process Communication,IPC)基础知识详见

5.2 进程间有哪些通信方式? | 小林codingicon-default.png?t=N7T8https://xiaolincoding.com/os/4_process/process_commu.html

9.1 匿名管道

#include <unistd.h>
int pipe(int pipefd[2]);
// 成功时返回0,失败时返回-1并设置errno
// pipefd    输出型参数,pipefd[0]保存管道读端文件描述符,pipefd[1]保存管道写端文件描述符

管道默认是阻塞的:如果管道中没有数据,read阻塞;如果管道满了,write阻塞。

如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。

如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),有进程向管道的写端写数据,那么该进程会收到一个信号SIGPIPE,通常会导致进程异常终止。

9.2 命名管道(FIFO)

创建:

  • mkfifo命令
mkfifo 管道的文件名
  • mkfifo函数
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname, mode_t mode);
// 成功时返回0,失败时返回-1并设置errno
// pathname    FIFO的文件名
// mode        文件权限

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于FIFO。

10. 进程间通信之共享内存

共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会称为一个进程用户空间的一部分,因此这种IPC机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。

共享内存是最高效的IPC机制,因为它不涉及进程之间的任何数据传输。这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则会产生竞态条件。因此,共享内存通常和其他进程间通信方式一起使用。

管理共享内存信息的结构体:

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 */
    ...
};

Linux共享内存的API都定义在sys/shm.h头文件中,包括4个系统调用:shmget、shmat、shmdt和shmctl。

shmget

shmget系统调用创建一段新的共享内存,或者获取一段已经存在的共享内存。其定义如下:

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
// 成功时返回共享内存的标识符,失败时返回-1并设置errno
// key       标识共享内存段,key_t是一个整型
// size      共享内存的大小
//           如果是创建新的共享内存,则size值必须被指定;如果是获取已经存在的共享内存,则可以把size设置为0
// shmflg    权限(如0666)和附加属性
//           附加属性:IPC_CREAT:创建共享内存
//                    IPC_EXCL:判断共享内存是否存在,需要和IPC_CREAT一起使用
//           示例:IPC_CREAT | IPC_EXCL | 0666

如果shmget用于创建共享内存,则这段共享内存的所有字节都被初始化为0,与之关联的内核数据结构shmid_ds将被创建并初始化。

shmat和shmdt

共享内存被创建/获取之后,我们不能立即访问它,而是需要先将它关联到进程的地址空间中。使用完共享内存之后,我们也需要将它从进程地址空间中分离。这两项任务分别由如下两个系统调用实现:

#include <sys/shm.h>
void* shmat(int shmid, const void* shmaddr, int shmflg);
// 成功时返回共享内存的首地址,失败时返回(void*)-1并设置errno
// shmid      共享内存的标识符,即shmget的返回值
// shmaddr    指定将共享内存关联到进程的哪块地址空间,通常为NULL,让内核指定
// shmflg     对共享内存的操作:SHM_RDONLY:只读模式
//                              0:读写模式
int shmdt(const void* shmaddr);
// 成功时返回0,失败时返回-1并设置errno
// shmaddr    共享内存的首地址

shmctl

shmctl系统调用控制共享内存的某些属性。其定义如下:

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
// 成功时返回0,失败时返回-1并设置errno
// shmid      共享内存的标识符,即shmget的返回值
// cmd        对共享内存的操作
//            IPC_STAT:获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中
//            IPC_SET :设置共享内存的状态,把buf的uid、gid、mode复制到共享内存的shmid_ds结构中
//            IPC_RMID:删除共享内存段
// buf        指向管理共享内存信息的结构体

11. 进程间通信之信号

11.1 Linux信号

前31个信号是常规信号,其余为实时信号。关于信号的详细信息可以用man 7 signal查看。

信号的默认动作:

Linux信号表
编号宏值默认动作含义
1SIGHUPTerm控制终端挂起
2SIGINTTerm键盘输入以中断进程(Ctrl+C)
3SIGQUITCore键盘输入使进程退出(Ctrl+\)
4SIGILLCore非法指令
5SIGTRAPCore断点陷阱,用于调试
6SIGABRTCore进程调用abort函数时生成该信号
7SIGBUSCore总线错误,错误内存访间
8SIGFPECore浮点异常
9

SIGKILL

Term终止一个进程。该信号不可被捕获或者忽略
10SIGUSR1Term用户自定义信号之一
11SIGSEGVCore非法内存段引用
12SIGUSR2Term用户自定义信号之二
13SIGPIPETerm往读端被关闭的管道或者socket连接中写数据
14SIGALRMTerm由alarm或setitimer设置的实时闹钟超时引起
15SIGTERMTerm终止进程。kill命令默认发送的信号就是SIGTERM
16SIGSTKFLTTerm早期的Linux使用该信号来报告数学协处理器栈错误
17SIGCHLDIgn子进程状态发生变化(退出或者暂停)
18SIGCONTCont启动被暂停的进程(Ctrl+Q)。如果目标进程未处于暂停状态,则信号被忽略
19SlGSTOPStop暂停进程(Ctrl+S)。该信号不可被捕获或者忽略
20SIGTSTPStop挂起进程(Ctrl+Z)
21SIGTTINStop后台进程试图从终端读取输入
22SIGTTOUStop后台进程试图往终端输出内容
23SIGURGIgnsocket连接上接收到紧急数据
24SIGXCPUCore进程的CPU使用时间超过其软限制
25SIGXFSZCore文件尺寸超过其软限制
26

SIGVTALRM

Term与SIGALRM类似,不过它只统计本进程用户空间代码的运行时间
27SIGPROFTerm与SIGALRM类似,它同时统计用户代码和内核的运行时间
28SIGWINCHIgn终端窗口大小发生变化
29SIGIOTermIO就绪,比如socket上发生可读、可写事件。因为TCP服务器可触发SIGIO的条件很多,故而SIGIO无法在TCP服务器中使用。SIGIO信号可用在UDP服务器中,不过也非常少见
30SIGPWRTerm对于使用UPS(Uninterruptable Power Supply)的系统,当电池电量过低时,SIGPWR信号将被触发
31SIGSYSCore非法系统调用

11.2 产生信号

  • 终端按键,如Ctrl+C产生SIGINT(2)信号,Ctrl+Z产生SIGTSTP(20)信号。
  • 硬件异常,如除0操作产生SIGFPE(8)信号,非法访问内存产生SIGSEGV(11)信号。
  • 软件异常,如往读端被关闭的管道或者socket连接中写数据产生SIGPIPE(13)信号,alarm或setitimer设置的定时器时间到产生SIGALRM(14)信号。
  • 系统调用,如kill函数给任何进程或进程组发送信号,raise函数给当前进程发送信号,abort函数给当前进程发送SIGABRT(6)信号。
  • kill命令,格式:kill [-信号的编号或宏值] PID,本质是调用kill函数实现的。

kill、raise、abort

#include <signal.h>
int kill(pid_t pid, int sig);
// 给任何进程或进程组发送信号
// 成功时返回0,失败时返回-1并设置errno
// pid>0     信号发送给PID为pid的进程
// pid=0     信号发送给本进程组内的其他进程
// pid=-1    信号发送给除init进程外的所有进程,但发送者需要拥有对目标进程发送信号的权限
// pid<-1    信号发送给组ID为-pid的进程组中的所有成员
// sig       要发送的信号的编号或宏值,0表示不发送任何信号

#include <signal.h>
int raise(int sig);
// 给当前进程发送信号
// 成功时返回0,失败时返回非0

#include <stdlib.h>
void abort(void);
// 给当前进程发送SIGABRT信号,异常终止该进程

alarm、setitimer

#include <unistd.h>
unsigned alarm(unsigned seconds);
// 设置定时器(闹钟),seconds秒之后向当前进程发送SIGALAR信号,该信号的默认动作是结束进程
// 之前没有定时器,返回0;之前有定时器,返回之前的定时器剩余的时间

#include <sys/time.h>
int setitimer(int which, const struct itimerval* restrict value, 
              struct itimerval* restrict ovalue);
// 设置周期性定时器(闹钟),将which指定的定时器设置为value指向的结构体中指定的值,精度微秒
// 成功时返回0,失败时返回-1并设置errno
// which     定时器类型
//           ITIMER_REAL   :以真实时间来计算,时间到发送SIGALRM
//           ITIMER_VIRTUAL:以该进程在用户态下所消耗的时间来计算,时间到发送SIGVTALRM
//           ITIMER_PROF   :以该进程在用户态和内核态下所消耗的时间来计算,时间到发送SIGPROF
// value     新的定时器
// ovalue    旧的定时器,一般不使用,通常为NULL

struct itimerval // 管理定时器的结构体
{
    struct timeval it_interval; // 时间间隔
    struct timeval it_value;    // 计时时长
};
// 第一次计时it_value时长发送信号,再往后的信号每隔一个it_interval发送一次
struct timeval // 管理时间的结构体
{        
    time_t      tv_sec;     //  秒数     
    suseconds_t tv_usec;    //  微秒    
};

11.3 捕捉信号

signal

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
// man 2 signal查询,手册说应该避免使用它,请使用sigaction
// 成功时返回前一次调用signal函数时传入的函数指针
// 如果是第一次调用signal,返回信号signum对应的默认处理函数指针SIG_DEF
// 失败时返回SIG_ERR并设置errno
// signum     要捕捉的信号的编号或宏值,除SIGKILL(9)和SIGSTOP(19)
// handler    捕捉到的信号如何处理
//            SIG_IGN:忽略信号
//            SIG_DFL:使用信号默认动作
//            自定义信号处理函数,是回调函数

sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact);
// 成功时返回0,失败时返回-1并设置errno
// signum    要捕捉的信号的编号或宏值,除SIGKILL(9)和SIGSTOP(19)
// act       捕捉到的信号如何处理
// oldact    上一次的处理动作,一般不使用,通常为NULL

struct sigaction
{
    void     (*sa_handler)(int); // SIG_IGN或SIG_DFL或自定义信号处理函数(回调函数)
    void     (*sa_sigaction)(int, siginfo_t *, void *); // 不常用
    sigset_t   sa_mask;  // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号
    int        sa_flags; // 通常为0,表示使用默认属性(即使用sa_handler)
    void     (*sa_restorer)(void); // 已弃用
};

11.4 信号集

11.4.1 未决信号集和阻塞信号集

信号递达、未决、阻塞的概念:

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)
  • 进程可以选择阻塞(Block)某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

图片来源:程序员成长之旅——进程信号_sigaction-CSDN博客

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

/* A `sigset_t' has a bit for each signal.  */

# define _SIGSET_NWORDS	(1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
    unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;

11.4.2 信号集函数

#include <signal.h>

int sigemptyset(sigset_t* set);
// 清空信号集(将信号集中所有标志位置为0)
// 成功时返回0, 失败时返回-1

int sigfillset(sigset_t* set);
// 设置所有信号(将信号集中所有标志位置为1)
// 成功时返回0, 失败时返回-1

int sigaddset(sigset_t* set, int signum);
// 将信号signum添加到信号集中(对应的标志位置为1)
// 成功时返回0, 失败时返回-1

int sigdelset(sigset_t* set, int signum);
// 将信号signum从信号集中删除(对应的标志位置为0)
// 成功时返回0, 失败时返回-1

int sigismember(const sigset_t* set, int signum);
// 判断signum是否在信号集中
// 在返回1,不在返回0,失败时返回-1
#include <signal.h>

int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
// 将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
// 成功时返回0, 失败时返回-1并设置errno
// how       如何对内核阻塞信号集进行处理
//           SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变
//                   假设内核中默认的阻塞信号集是mask, mask | set
//           SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞
//                   mask &= ~set
//           SIG_SETMASK:覆盖内核中原来的值            
// set       已经初始化好的用户自定义的信号集
// oldset    保存设置之前的内核中的阻塞信号集的状态,可以是NULL

int sigpending(sigset_t* set);
// 获取内核中的未决信号集
// 成功时返回0, 失败时返回-1并设置errno

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1086775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux传统跨进程通信原理

文章目录 前言一、进程隔离二、进程空间划分&#xff1a;用户空间(User Space)/内核空间(Kernel Space)三、系统调用&#xff1a;用户态与内核态四、Linux下传统IPC跨进程通信原理1、发送进程通过系统调用&#xff0c;将需要发送的数据拷贝到Linux进程的内核空间中的缓存区(数据…

百度智能云千帆大模型平台 2.0 产品技术解析

本文整理自 2023 年 9 月 5 日百度云智大会 - 智能计算&大模型技术分论坛&#xff0c;百度智能云 AI &大数据平台总经理忻舟的主题演讲《百度智能云千帆大模型平台 2.0 产品技术解析》。 这是关于技术主题的论坛&#xff0c;我首先问大家三个开发者的小问题。 第一个问…

tez作业运行慢

文章目录 问题现象&#xff1a;排查思路查看task运行概况查看map和reduce container的日志初步结论 继续排查container数量差异大分片计算异常 结论 问题现象&#xff1a; 每天调度的一个任务在某天突然运行时长多了好几倍&#xff0c;平时30m左右&#xff0c;那天运行了4个小…

Ubuntu 22.04‘Temporary failure resolving‘ 解决方案

终极解决方案 首先安装resolvconf sudo apt-get install resolvconf 使用 cd /etc/resolvconf/resolv.conf.d/ 进入文件夹&#xff0c;使用 ls 查看目录&#xff0c;会显示 base head tail 使用 sudo vim base 编辑base文件&#xff0c; 进入时为空&#xff0c;点击 i 添加 …

【架构艺术】(零) 环境搭建

写在前面 今天尝试了如systemC,Chisel,MyHDL等方式来进行功能仿真&#xff0c;并生成波形到Wavedrom格式&#xff0c;后来发现对于学习这些简单架构&#xff0c;还是脑子里面根据规则进行仿真或者是编写verilog代码进行仿真即可。 所以我们的环境依赖只有&#xff1a;安装waved…

【PostgreSQL启动,停止命令(重启)】

找到 /usr/lib/systemd/system文件夹路径看是否包含 postgresql服务 关闭服务&#xff1a; systemctl stop postgresql-12.service启动服务 systemctl start postgresql-12.service重启服务 systemctl restart postgresql-12查看状态 systemctl status postgresql-12.servi…

区分Cookie,Session,Token

Cookie 由于HTTP 协议是一个无状态协议&#xff0c;客户端向服务器发请求&#xff0c;服务器返回响应。并且你每次都要输入账号和密码进行登录&#xff0c;对于用户来说非常的麻烦&#xff01;这种背景下&#xff0c;就产生了 Cookie cookie 存储在客户端&#xff1a; cookie…

Essential Steps in Natural Language Processing (NLP)

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

C语言 | 浮点数在内存中存储方式

浮点数其实在内存中也是以2进制的形式存储的&#xff0c;但是它不是以原码、反码、补码的形式存储的。 常见的浮点数&#xff1a; 3.14159 1E10【科学计数法1.0*10^10】 eg&#xff1a;1.2312.3*10^-10.123*10^1 浮点数家族包括&#xff1a;float、double、long double类型…

C语言水平测试题 过关斩将(3)辗转相除法,前n项求和,整数的正序分解,求最大公约数

我的个人主页&#xff1a;☆光之梦☆的博客_CSDN博客-C语言基础语法&#xff08;超详细&#xff09;领域博主 欢迎各位 &#x1f44d;点赞 ⭐收藏 &#x1f4dd;评论 我的专栏&#xff1a;C语言基础语法&#xff08;超详细&#xff09;_☆光之梦☆的博客-CSDN博客&#xff08;这…

Linux 部署 GitLab idea 连接

概述 GitLab 是一个开源的代码管理平台&#xff0c;使用 Git 作为版本控制工具&#xff0c;提供了 Web 界面和多种功能&#xff0c;如 wiki、issue 跟踪、CI/CD 等。 GitLab 可以自托管或使用 SaaS 服务&#xff0c;支持多种操作系统和执行器。 GitLab 可以帮助软件开发团队…

有 AI,无障碍,AIoT 设备为视障人群提供便利

据世界卫生组织统计&#xff0c;全球共 22 亿人视力受损&#xff0c;包含 2.85 亿视障人群和 3,900 万全盲人群。而且&#xff0c;这一数字将随老龄化加剧不断增加。 虽然视障人群面临着诸多不便&#xff0c;但是针对视障人群的辅助设备却存在成本高、维护困难、操作复杂等问题…

小流域洪水分析模拟预报设计及代码实现

应用说明&#xff1a; 利用无人机、卫星等技术&#xff0c;获取小流域洪水模拟分析所需的数据&#xff0c;并将其与模型进行结合&#xff0c;提高模拟精度&#xff1b; 探索小流域洪水模拟分析与城市规划、土地利用等方面的关系&#xff1b; 小流域河流洪水模拟计算分析是一项…

Java编程第9讲——CountDownLatch、CyclicBarrier和Semaphore(万字详解)

在JDK的并发包&#xff08;JUC&#xff09;里提供了几个非常有用的并发工具类。CountDownLatch、CyclicBarrier和Samaphore工具类提供了一种并发流程控制的手段&#xff0c;这同样也是面试和工作中的一个重要知识点&#xff0c;本文将从它们的定义、常用方法、代码示例及核心源…

STM32Cube高效开发教程<基础篇>(一)----概述

一、 STM32系列器件和开发工具发展历史 1.1 开发工具发展史 2014年HAL/LL库和STM32Cube是ST公司STM32Cube计划的产物,旨在提高开发效率。2019年4月,ST公司退出自己的IDE软件STM32CubeIDE,完善了STM32Cube生态系统。 1.2 STM32系列器件 1.3 器件驱动库 标准外设库( Standar…

Python Django 之连接 Mysql 数据库详解

文章目录 1 概述1.1 Mysql 下载和安装1.2 菜单目录 2 ORM 框架2.1 连接 Mysql 模块&#xff1a;mysqlclient2.2 创建数据库2.3 连接 Mysql2.4 创建表2.5 增删改查 3 扩展3.1 ERROR&#xff1a;2026, SSL connection error: unknown error number 1 概述 1.1 Mysql 下载和安装 …

windows安装nvm

源代码 下载 下一步一下步安装即可 检查是否安装成功 nvm出现上面的代码即可安装成功 常用命令 查看目前安装的node版本 nvm list [available]说明没有安装任何版本&#xff0c;下面进行安装 nvm install 18.14使用该版本 node use 18.14.2打开一个新的cmd输入node -…

Self-Instruct

本篇工作利用LLM的生成能力&#xff0c;来产生大量指令数据集&#xff08;指令、输入、输出&#xff09;&#xff0c;无需人工标注数据。 其中&#xff0c;在对任务判别的时候&#xff0c;需要区分是输出优先还是输入优先&#xff1a; 输入优先没问题&#xff0c;符合人类直觉…

面试题:说说Java线程的状态及转换

文章目录 为何要了解Java线程状态Java线程状态转换图Java线程有哪些状态&#xff1f;关于wait()放在while循环的疑问BLOCKED 和 WAITING 状态的区别和联系 为何要了解Java线程状态 线程是 JVM 执行任务的最小单元&#xff0c;理解线程的状态转换是理解后续多线程问题的基础。 …

加持智慧医疗,美格智能5G数传+智能模组让就医触手可及

智慧医疗将云计算、物联网、大数据、AI等新兴技术融合赋能医疗健康领域&#xff0c;是提高医疗健康服务的资源利用效率&#xff0c;创造高质量健康医疗的新途径。《健康中国2030规划纲要》把医疗健康提升到了国家战略层面&#xff0c;之后《“十四五”全面医疗保障规划》等一系…