进程与线程 -- C/C++

进程与线程

进程

程序概念:

​ 存放在磁盘上的指令和数据的有序集合(文件)

​ 静态的

进程概念:

​ 执行一个程序所分配的资源的总称

​ 进程就是程序的一次执行过程

​ 动态的,包括创建、调度、执行和消亡

​ 进程包括代码、用户数据和 系统数据(进程控制块、cpu寄存器的值、堆栈)

进程控制块(pcb)

​ 进程标识PID

​ 进程用户

​ 进程状态、优先级

​ 文件描述符表

进程类型

交互进程:在shell下启动。以在前台运行,也可以在后台运行。

批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行。

守护进程:和终端无关,一直在后台运行。

进程状态

运行态:进程正在运行或者准备运行

等待态:进程在等待一个事件的发生或某种系统资源 – 分为可中断和不可中断两种状态

停止态:进程被终止,收到信号后可以继续运行

死亡态:已终止的进程,但是pcb没有被释放(僵尸进程)

查看进程信息

ps 查看系统进程快照 ps -ef | grep ps aux(显示进程当前状态)

top 查看进程动态信息 top

/proc 查看进程详细信息

创建进程 - fork

pid_t fork(void);

以下代码在linux中运行:

#include 
#include 
int main()
{
    pid_t pid;
    pid = fork();

    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        printf("child process; my pid is %d\n", getpid());
    }
    else
    {
        printf("parent process; my pid is %d\n", getpid());
    }
}

父子进程:

  1. 子进程继承了父进程内容

  2. 父子进程有独立的地址空间,互不影响

  3. 若父进程先结束

    • 子进程成为孤儿进程,被init(1)进程收养
    • 子进程变成后台进程
  4. 若子进程先结束

    • 父进程如果没有及时回收,子进程变成僵尸进程

子进程从何处开始运行? 从fork()之后语句开始执行。

父子进程谁先执行? 不确定,由内核调度决定

父进程能否多次调用fork()? 子进程呢? 都可以

结束进程 - exit/_exit

#include 
#include 
void exit(int status);
void _exit(int status); // 丢弃缓冲区

结束当前进程,并且将status 返回

exit结束进程时会刷新(流)缓冲区

以下代码在linux中运行:

#include 
#include 
#include 
int main()
{
    printf("aaa");
    _exit(0);
    printf("bbbb");
}

exec函数族

调用exec函数族执行某个程序

进程当前内容被指定程序替换

实现让父子进程执行不同的程序

  • 父进程创建子进程
  • 子进程调用exec函数族
  • 父进程不受影响

进程 - execl/execlp

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);

成功时执行置顶程序;失败时候返回EOF

path 执行程序名称,包含路径

arg… 传递给执行程序的参数列表

file 执行程序名称,在PATH中查找

#include 
#include 
#include 
int main()
{
    if(execl("/bin/ls", "ls", "-al", "/etc", NULL) < 0)
    {
	perror("execl");
    }
}
#include 
#include 
#include 
int main()
{
    if(execlp("ls", "ls", "-al", "/etc", NULL) < 0)
    {
	    perror("execlp");
    }
}

进程 - execv/execvp

int execv(const char *path,  char *const arg[]);
int execvp(const char *file, char *const arg);

成功时执行置顶程序;失败时候返回EOF

arg… 封装成指针数组的形式

#include 
#include 
#include 
int main()
{
    char *argv[] = { "ls", "-al", "/etc", NULL};
    if(execv("/bin/ls",argv) < 0)
    {
	    perror("execv");
    }
    if(execvp("ls",argv) < 0)
    {
	    perror("execvp");
    }
}

进程 - system

int system(const char *command);

进程回收

  • 子进程结束时候由父进程回收
  • 孤儿进程由init进程回收
  • 若没有及时回收会出现僵尸进程

进程回收 - wait

pid_t wait(int *status);

成功时候返回进程好;失败时候返回EOF

若子进程没有结束,父进程一直阻塞

若有多个子进程,则哪个先结束先回收哪个子进程

status 置顶保存子进程返回值和结束方式的地址

status 为NULL标志直接释放子进程的PCB

nclude <stdio.h>
#include 
#include 
#include 
#include 

int main()
{
    int status;
    pid_t pid;
    if((pid = fork()) < 0)
    {
        perror("fork");
        return -1;
    }

    if(pid == 0)
    {
        sleep(1); 
        exit(2);
    }
    else
    {
        wait(&status);
        printf("%x\n", status);
    }
}

子进程通过exit/_exit/return返回某个值(0-255);

父进程调用wait(&status)回收

  • WIFEXITED(status) 判断子进程是否正常结束
  • WEXITSTATUS(status) 获取子进程的返回值
  • WIFSIGNALED(status) 判断子进程是否被信号结束
  • WTERMSIG(status) 获取结束子进程的信号

进程回收 - waitpid

pid_t waitpid(pid_t pid, int *status, int option);

成功时返回回收的子进程的pid或0;失败时返回EOF

pid可用于指定回收哪个子进程或者任意子进程

status指定用于保存子进程返回值和结束方式的地址

option指定回收方式 0或者 WNOHANG

waitpid(pid, &status, 0);
waitpid(pid, &status, WNOHANG);
waitpid(-1, &status, 0);
waitpid(-1, &status, WNOHANG);

守护进程

守护进程(Daemon)是linux三种进程类型之一

通常在系统启动时运行,系统关闭时结束

Linux系统中大量使用,很多服务程序以守护进程形式运行

特点

  • 始终在后台运行
  • 独立于任何终端
  • 周期性的执行某种任务或者等待处理特定事件

守护进程 - 会话、控制终端

  • Linux以会话(session)、进程组的方式管理进程
  • 每个进程属于一个进程组(子进程和父进程在同一个进程组)
  • 会话是一个或者多个进程组的集合。通常用户打开一个终端时,系统会创建一个会话。所有通过该终端运行的进程都属于这个会话
  • 终端关闭时候,多有相关的进程都会被结束

守护进程 - 创建

  1. 创建子进程,父进程退出
if(fork>0)
{
    exit(0);
}
  • 子进程变成孤儿进程,被init进程收养
  • 子进程在后台运行
  1. 子进程创建新会话
if(setsid() < 0)
{
    exit(-1);
}
  • 子进程成为新的会话组长
  • 子进程脱离原先的终端
  1. 更改当前工作目录
chdir("/");
chdir("/tmp");
  • 守护进程一直在后台运行,其工作目录不能被卸载
  • 重新设定当前工作目录cwd
  1. 重设文件权限掩码
if(umask(0) < 0)
{
    exit(-1);
}
  • 文件权限掩码设置为0
  • 只影响当前进程
  1. 关闭打开的文件描述符
for(int i = 0; i < getdtablesize(), i++)
{
    close(i);
}
  • 关闭所有从父进程继承的打开文件
  • 已经脱离终端, stdin/ stdout/ stderr无法在使用
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t pid;
    FILE *fp;
    time_t t;
    if((pid = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    if(pid > 0)
    {
        exit(0);
    }
    setsid();
    umask(0);
    chdir("/tmp");
    for(int i = 0; i < getdtablesize(); i++)
    {
        close(i);
    }

    if((fp = fopen("time.log","a+")) == NULL)
    {
        perror("fopen");
        exit(-1);
    }
    while(1)
    {
        time(&t);
        fprintf(fp, "%s", ctime(&t));
        fflush(fp); 
        sleep(1);
    }
}

线程

进程:

进程有独立的地址空间

Linux为每个进程创建task_struct

每个进程都参与内核调度,互不影响

线程:

进程在切换时候系统开销比较大

很多操作系统引入了轻量级LWP

同一个进程中的线程共享相同的地址空间

Linux不区分进程、线程

特点:

通常线程是指共享相同地址空间的多个任务

使用线程的好处

  • 大大提高了任务切换效率
  • 避免了额外的TLB & cache的刷新

一个进程中的线程共享以下的资源

  • 可执行的指令
  • 静态数据
  • 进程中打开的文件描述符
  • 当前工作目录
  • 用户ID
  • 用户组ID

Pthread线程库

  1. pthread 线程库提供了如下基本操作
    • 线程创建
    • 线程回收
    • 结束线程
  2. 同步和互斥机制
    • 信号量
    • 互斥锁

线程创建 - pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg);
  • 成功时候返回0, 失败时返回错误码
  • thread是线程对象
  • attr线程属性,NULL代表默认属性
  • routine线程执行的函数
  • 传递给routine的参数

线程回收- pthread_join

int pthread_join(pthread_t thread, void **retval);
  • 成功时候返回0, 失败时返回错误码
  • thread是要回收的线程对象
  • 调用线程阻塞知道thread结束
  • *retval接收线程thread的返回值

线程结束- pthread_exit

void pthread_exit(void *retval);
  • 结束当前线程
  • retval可被其他线程通过pthread_join获取
  • 线程私有资源被释放
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

char message[32] = "hello world";
void* thread_func(void *arg);

int main()
{
    pthread_t a;
    void *result;
    if(pthread_create(&a, NULL, thread_func, NULL) != 0)
    {
        perror("error");
        return -1;
    }

    pthread_join(a, &result);
    printf("result is %s\n", result);
    printf("message is %s\n", message);
}
void* thread_func(void *arg)
{
    sleep(1);
    strcpy(message, "marked by thread");
    pthread_exit("aaa");
}

线程间通信

线程共享统一进程的地址空间

优点: 线程间通信很容易,通过全局变量交换数据

缺点:多个线程访问共享数据时需要同步或者互斥机制

线程通信 - 同步

  • 同步指的是多个任务按照约定的先后顺序相互配合完成一件事情

  • 信号量

  • 信号量来决定线程是继续运行还是阻塞

信号量

信号量代表某一类资源,其值表示系统中该资源的数量

信号量是一个受保护的变量,只能通过三种操作来访问

  • 初始化
  • P操作(申请资源)
  • V操作(释放资源)

Posix信号量

posix中定义了两类信号量

  • 无名信号量(基于内存的信号量)
  • 有名信号量(线程之间和进程之间)

pthread库常用的信号量操作函数如下:

#include 
int sem_init(sem_t *sem, int pshared, ussigned int value);
int sem_wait(sem_t *sem); // p操作
int sem_post(sem_t *sem); // v操作

信号量初始化 - sem_init

int sem_init(sem_t *sem, int pshared, unsigned int val);

成功时候返回0, 失败时候返回EOF

sem 指向要初始化的信号量对象

pshared 0 - 线程间 1 - 进程间

信号量P/V操作

int sem_wait(sem_t *sem); // p操作
int sem_post(sem_t *sem); // v操作

成功时候返回0, 失败时候返回EOF

sem 指向要操作的信号量对象

信号量代码示例:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

char buf[32];
sem_t sem;
void* function(void *arg);
int main()
{
    pthread_t a_thread;

    if(sem_init(&sem, 0, 0) < 0)
    {
        perror("sem_init");
        return -1;
    }

    if(pthread_create(&a_thread, NULL, function, NULL) < 0)
    {
        printf("failed to pthread_create");
        return -1;
    }
    printf("input quit to exit");
    do
    {
        fgets(buf , 32, stdin);
        sem_post(&sem);
    }while(strncmp(buf, "quit", 4) !=0 );
    return 0;
}

void* function(void *arg)
{
    while(1)
    {
        sem_wait(&sem);
        printf("you enter %d cahraters\n", strlen(buf));
        fflush(stdout);
    }
}

使用两个信号量做到数据严格同步:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

char buf[32];
sem_t sem_r, sem_w;
void* function(void *arg);
int main()
{
    pthread_t a_thread;

    if(sem_init(&sem_r, 0, 0) < 0)
    {
        perror("sem_init");
        return -1;
    }
    if(sem_init(&sem_w, 0, 1) < 0)
    {
        perror("sem_init");
        return -1;
    }

    if(pthread_create(&a_thread, NULL, function, NULL) < 0)
    {
        printf("failed to pthread_create");
        return -1;
    }
    printf("input quit to exit");
    do
    {
        sem_wait(&sem_w);
        fgets(buf , 32, stdin);
        sem_post(&sem_r);
    }while(strncmp(buf, "quit", 4) !=0 );
    return 0;
}

void* function(void *arg)
{
    while(1)
    {
        sem_wait(&sem_r);
        printf("you enter %d cahraters\n", strlen(buf));
        fflush(stdout);
        sem_post(&sem_w);
    }
}

线程通信 - 互斥

  • 临界资源

    一次只允许一个任务(进程、线程)访问的共享资源

  • 临界区

    访问临界区的代码

  • 互斥机制

    mutex互斥锁

    任务在访问临界资源前申请锁,访问完之后释放锁

互斥锁

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_init:

成功时候返回0,失败时候返回错误码

mutex 指向要初始化的互斥锁对象

attr 互斥锁属性,NULL表示缺省属性

pthread_mutex_lock:

成功时候返回0,失败时候返回错误码

mutex 指向要初始化的互斥锁对象

如果无法获得锁,任务阻塞

pthread_mutex_unlock:

成功时候返回0,失败时候返回错误码

mutex 指向要初始化的互斥锁对象

执行完临界区要及时释放锁

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

unsigned int count, value1, value2;
pthread_mutex_t lock;
void* function(void *arg);
#define LOCKM_
int main()
{
    pthread_t a_thread;
    if(pthread_mutex_init(&lock, NULL) != 0)
    {
        perror("pthread_mutex_init");
        return -1;
    }
    if(pthread_create(&a_thread, NULL, function, NULL) != 0)
    {
        perror("pthread_create");
        return -1;
    }
    while(1)
    {
        count++;
#ifdef LOCKM_
        pthread_mutex_lock(&lock);
#endif
        value1 = count;
        value2 = count;
#ifdef LOCKM_
        pthread_mutex_unlock(&lock);
#endif
    }
    return 0;
}

void* function(void *arg)
{
    while(1)
    {
#ifdef LOCKM_
        pthread_mutex_lock(&lock);
#endif
        if(value1 != value2)
        {
            printf("value1 = %d , value2 = %d\n", value1, value2);
            fflush(stdout);
            usleep(1000);
        }
#ifdef LOCKM_
        pthread_mutex_unlock(&lock);
#endif
    }
}

进程间通信

  • 早期UNIX进程间通信方式

    无名管道(pipe)

    有名管道(fifo)

    信号(signal)

  • System V IPC

    共享内存(share memory)

    消息队列(message queue)

    信号灯集(semaphore set)

  • 套接字(socket)

无名管道

  • 只能用于具有亲缘关系的进程之间的通信
  • 单工通信模式,具有固定的读端和写端
  • 无名管道创建时候会返回两个文件描述符,分别用于读写管道
int pipe(int pfd[2]);

pipe:

成功时候返回0,失败时候返回EOF

pfd包含两个元素的整形数组,用于保存文件描述符

pfd[0]用于读管道,pfd[1]用于写管道

#include 
#include 
#include 
#include 
int main()
{
    pid_t pid1, pid2;
    char buf[32];
    int pfd[2];
    if(pipe(pfd) < 0)
    {
        perror("pipe");
        return -1;
    }
    if((pid1 = fork()) <0)
    {
        perror("fork");
        return -1;
    }
    else if(pid1 == 0)
    {
        strcpy(buf, "im process1");
        write(pfd[1], buf, 32);
        exit(0);
    }
    else
    {
        if((pid2 == fork()) < 0)
        {
            perror("fork2");
            return -1;
        }
        else if(pid2 == 0)
        {
            sleep(1);
            strcpy(buf, "im process2");
            write(pfd[1], buf, 32);
        }
        else
        {
            wait(NULL);
            read(pfd[0], buf, 32);
            printf("%s\n", buf);
            wait(NULL);
            read(pfd[0], buf, 32);
            printf("%s\n", buf);
        }
    }
    return 0;
}

读无名管道

  • 写端存在

    有数据 – read返回实际读取的字节数量

    无数据 – 进程读阻塞

  • 写端不存在

    有数据 – read返回实际读取的字节数量

    无数据 – read返回0

写无名管道

  • 读端存在

    有空间 – write返回实际写入的字节数

    无空间 – 空间不足时候不保证原子操作(有多少写多少,剩余等有空间再写),进程写阻塞

  • 读端不存在不允许写入

    管道断裂

有名管道

对应管道文件,可用于任意进程之间通信

打开管道时候可以指定读写方式

通过文件I/O操作,内容存放在内存中

int mkfifo(const char *path, mode_t mode);

mkfifo:

成功时候返回0,失败返回EOF

path 创建管道文件路径

mode 管道文件权限,如0666

信号

  • 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

  • linux内核通过信号通知用户进程,不同的信号类型代表不同的事件

  • Linux对早期unix信号机制进行了扩展

  • 进程对信号有不同的响应方式

    缺省方式

    忽略信号

    捕捉信号

常用的信号

信号名 含义 默认操作
SIGHUP 该信号在用户终端关闭时候产生,通常是发送给和该终端关联的会话内所有的进程 终止
SIGINT 该信号在用户键入INTR制度时候产生,内核发送此信号到当前终端的所有前台进程(ctrl+c) 终止
SIGQUIT 该信号和sigint类似,但是由于quit字符来产生(ctrl+\) 终止
SIGLL 该信号在一个进程企图执行一条非法指令时候产生 终止
SIGSEV 该信号在非法访问内存时候产生,如野指针、缓冲区溢出 终止
SIGPIPE 当进程往一个没有读端的管道中写入时产生,代表管道断裂 终止
SIGKILL 该信号用来结束进程,并且不能被捕捉和忽略 终止
SIGSTOP 该信号用于暂停继承,并且不能被捕捉和忽略 暂停进程
SIGTSTP 该信号用于暂停继承,用户可以键入SUSP字符发出这个信号(ctrl+z) 暂停进程
SIGCONT 该信号让进程进入运行态 继续运行
SIGALRM 该信号用于通知进程定时器时间已到 终止
SIGUSR1/2 该信号保留给用户程序使用 终止

信号发送

int kill(pid_t pid, int sig);
int raise(int sig);  
int alarm(unsigned int seconds); // 创建一个定时器,如果为0取消当前定时器
int pause(void);  // 进程会一直阻塞,知道被信号终端

设置信号的响应方式

void (*signal(int signo, void(*handker)(int)))(int);

System V IPC

包括共享内存,消息队列和信号灯集

每个IPC对象有唯一的ID

IPC对象创建后一直存在,知道被显式的删除

每一个IPC对象有一个关联的key

key_t ftok(const char *path, int proj_id);

ftok:

成功时候返回key,失败返回EOF

path是存在可访问的文件路径

proj_id是用于生成key的数字,不能是0

#include 
#include 
#include 
#include 
#include 

int main()
{
	key_t key;
    if((key=ftok(".", 'a')) == -1)
    {
        perror("ftok");
        return -1;
    }
}

共享内存

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据拷贝

  • 共享内存在内核空间创建,可以被进程映射到用户空间访问,使用灵活

  • 由于多个进程可以同时访问共享内存,因此需要同步和互斥机制配合使用

使用步骤

  1. 创建/打开共享内存
  2. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
  3. 读写共享内存
  4. 撤销共享内存映射
  5. 删除共享内存对象
// 1. 创建/打开共享内存
int shmget(kety_t key, int size, int shmflg);
// 2. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 3. 读写共享内存
...
// 4. 撤销共享内存映射
int shmdt(void *shmaddr);
// 5. 设置共享内存, 删除共享内存对象
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmget:

key 和共享内存关联的key, IPC_PRIVATE或者ftok生成

shmflg 共享内存标志位 IPC_CREATE | 0666

示例代码(不能直接运行)

// 创建私有
int main()
{
	int shmid;
	if((shmid = shmget(IPC_PRIVATE, 512, 0666) < 0)
	{
        perror("shmget");
        return -1;
	}
}
// 创建关联key的
int main()
{
    key_t key;
	int shmid;
    
    if((key = ftok('.', 'm') == -1)
    {
        perror("ftok");
        return -1;
    }
    
	if((shmid = shmget(key, 1024, IPC_CREEATE|0666) < 0)
	{
        perror("shmget");
        return -1;
	}
}     

shmat:

shmid 要映射的共享内存ID

shmaddr 映射后的地址,NULL表示由系统自动映射

shmflg 标志位, 0表示可读可写,SHM_RDONLY表示只读

  • 通过指针访问共享内存,指针类型取决于共享内存存放的数据类型

示例代码(不能直接运行)

char *addr;
int shmid;
...
if(addr = (char*)shmat(shmid, NULL, 0) == (cahr *)-1);
{
    perror("shmat");
    return -1;
}
fgets(addr , N , stdin);

shmdt:

不适用共享内存时候应该撤销映射关系

进程结束时候,操作系统会自动撤销

shmctl:

shmid 要操作的共享内存ID

cmd 要执行的操作 IPC_STAT(获取属性) IPC_SET(设置属性) IPC_RMID(删除ID)

struct shmid_ds *buf 表示共享内存属性

共享内存 – 注意事项

每块共享内存大小有限制

  • ipcs -l
  • cat /proc/sys/kernel/shmmax

共享内存删除的时间点

  • shmct(shmid, IC_RMID, NULL) 添加删除标记
  • nattach变成0时候真正删除

消息队列

  • 消息队列是IPC对象的一种
  • 消息队列由消息队列ID来唯一标识
  • 消息队列就是一个消息列表,用户可以在消息队列中添加消息、读取消息
  • 消息队列可以按照类型来发送、接收消息

使用步骤:

  1. 打开/创建见消息队列
  2. 向消息队列发送消息
  3. 从消息队列接收消息
  4. 控制消息队列
#include 
#include 
// 1. 打开/创建见消息队列
int msgget(key_t key, int msgflg);
// 2. 向消息队列发送消息
int msgsnd(int msgid, const void *msgp, size_t size, int msgflg);
// 3. 从消息队列接收消息
int msgrcv(int msgid, void *msgp, size_t size, long msgtype, int msgflg);
// 4. 控制消息队列
int msgctl(int msgid, int cmd, struct msqid_df *buf);

msgget:

key 和消息队列关联的key IPC_PRIVATE或ftok

msgflg 标志位IPC_CREATE | 0666

示例代码(不能直接运行)

int main()
{
    int msgid;
    key_t key;
    if((key = ftok(".", "q")) == -1)
    {
        perror("ftok");
        return -1;
    }
    if((msgid = msgget(key, IPC_CREATE|0666)) < 0)
    {
        perror("msgget");
        return -1;
    }
    return 0;
}

msgsnd:

msgid 消息队列ID

msgp 消息缓冲区地址

size 发送的消息长度(正文长度)

msgflg 标志位0或者IPC_NOWAIT

示例代码(不能直接运行)

typedef struct
{
	long mtype;
    char mtext[64];
}MSG
    
#define LEN (sizeof(MSG) - sizeof(long))

int main()
{   
	MSG buf;
	buf.mtype = 100;
    fgets(buf.mtext, 64, stdin);
    msgsnd(msgid, &buf, LEN, 0);
    ...
    return 0;
}    

消息格式

  • 通信双方首先定义好统一的消息格式
  • 用户根据应用需求定义结构体类型
  • 首成员为long,代表消息类型(正整数)
  • 其他成员为消息正文

msgrcv:

msgid 消息队列id

msgp 消息缓冲区地址

size 接收的消息长度(正文长度)

msgtype 指定接收的消息类型

msgflg 标志位0或者IPC_NOWAIT

示例代码(不能直接运行)

typedef struct
{
	long mtype;
    char mtext[64];
}MSG
    
#define LEN (sizeof(MSG) - sizeof(long))

int main()
{   
	MSG buf;
	buf.mtype = 100;
    fgets(buf.mtext, 64, stdin);
    if(msgrcv(msgid, &buf, LEN, 100,0) < 0)
    {
    	perror("msgrcv");
        return -1;
    }
    ...
    return 0;
}    

msgctl:

msgid 消息队列ID

cmd 要执行的操作 IPC_STAT / IPC_SET / IPC_RMID

struct msqid_df*buf 表示消息队列属性地址

(消息被删除会立刻删除)

信号灯

信号灯又叫信号量,用于线程或者进程同步或者互斥的机制

信号灯类型

posix 无名信号灯

posix 有名信号灯

信号灯的含义 计数信号灯

system v 信号灯

  • 是一个或者多个计数信号灯的集合
  • 可以同时操作集合中的多个信号灯
  • 申请多个资源时候避免死锁

使用步骤

  1. 打开/创建信号灯
  2. 信号灯初始化
  3. PV操作
  4. 删除信号灯
// 1.打开/创建信号灯
int semget(key_t key, int nsems, int semflg);
// 2. 信号灯初始化
int semctl(int semid, int semnum. int cmd, ...)
// 3. PV操作
int semop(int semid, struct sembuf *sops, size_t nsops);
// 4. 删除信号灯  semctl

semget:

key 和信号灯关联的key IPC_PRIVATE或ftok

nsems 集合中包括的信号灯的个数

semflg 标志位IPC_CREATE | 0666 IPC_EXCL

semctl:

semid 信号灯的ID

semnum 要操作的信号灯的编号

cmd 执行的操作 SETVAL IPC_RMID

union semun 取决于cmd

semop:

semid 要操作的信号灯集

sops 描述对信号灯操作的结构体

nsops 要操作的信号灯个数

struct sembuf 
{
    short semnum;  // 信号灯编号
    short sem_op;  // -1  p操作  //  1  v操作
    short sem_flg; // 0  IPC_NOWAIT
}

你可能感兴趣的:(C/C++日常,linux)