进程和线程通信学习笔记

进程的概念:执行一个程序所分配的资源的总称

进程是程序的一次执行,是动态的,包括创建、调度、执行和消亡

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

进程和程序内容区别

进程包含的内容

代码段:程序执行代码(机器码)

数据段:已初始化的全局变量

BSS段:存放程序中未初始化的全局变量

堆(heap):malloc等函数分配内存

栈(stack):局部变量,函数参数,函数的返回值

进程控制块(PCB):PID, 进程用户,进程状态,进程优先级,文件描述符表(1024)

进程类型

交互进程:可以在前台运行,也可以在后台运行

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

守护进程:一直在后台运行

进程状态

运行态、等待态、停止态、死亡态

进程常用命令

Ps:查看系统进程快照

top: 查看进程动态信息

/proc : 查看进程详细信息

ps 命令详细参数:

-e:显示所有进程

-l:长格式显示更加详细的信息          -f 全部列出,通常和其他选项联用

top    查看进程动态信息              shift +> 后翻页

shift +< 前翻页                      top -p PID  查看某个进程

jobs   查看后台进程                  bg     将挂起的进程在后台运行

fg     把后台运行的进程放到前台运行

ctrl+z  把运行的前台进程转为后台并停止。

表头

含义

F

进程标志,说明进程的权限,常见的标志有两个:

  • 1:进程可以被复制,但是不能被执行;
  • 4:进程使用超级用户权限;

S

进程状态。进程状态。常见的状态有以下几种:

  1. -D:不可被唤醒的睡眠状态,通常用于 I/O 情况。
  2. -R:该进程正在运行。
  3. -S:该进程处于睡眠状态,可被唤醒。
  4. -T:停止状态,可能是在后台暂停或进程处于除错状态。
  5. -W:内存交互状态(从 2.6 内核开始无效)。
  6. -X:死掉的进程(应该不会出现)。
  7. -Z:僵尸进程。进程已经中止,但是部分程序还在内存当中。
  8. -<:高优先级(以下状态在 BSD 格式中出现)。
  9. -N:低优先级。
  10. -L:被锁入内存。
  11. -s:包含子进程。
  12. -l:多线程(小写 L)。
  13. -+:位于后台。

UID

运行此进程的用户的 ID;

PID

进程的 ID;

PPID

父进程的 ID;

C

该进程的 CPU 使用率,单位是百分比;

PRI

进程的优先级,数值越小,该进程的优先级越高,越早被 CPU 执行;

NI

进程的优先级,数值越小,该进程越早被执行;

ADDR

该进程在内存的哪个位置;

SZ

该进程占用多大内存;

WCHAN

该进程是否运行。"-"代表正在运行;

TTY

该进程由哪个终端产生;

TIME

该进程占用 CPU 的运算时间,注意不是系统时间;

CMD

产生此进程的命令名;

改变进程优先级

1)nice   按用户指定的优先级运行进程

     nice [-n NI值] 命令

NI 范围是 -20~19。数值越大优先级越低

普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。

普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。

只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。

2)renice   改变正在运行进程的优先级

    renice [优先级] PID

创建进程、子进程

子进程:由另外一个进程所创建的进程

#include  <unistd.h>   //头文件

pid_t  fork(void);

创建新的进程,失败时返回-1

成功时父进程返回子进程的进程号,子进程返回0

通过fork的返回值区分父进程和子进程

注意:1 子进程只执行fork之后的代码

  1. 父子进程执行顺序是操作系统决定的。

子进程与父进程关系

子进程继承了父进程的内容

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

父子进程的执行顺序由操作系统来安排

若父进程先结束:子进程成为孤儿,被init进程收养,子进程变成后台进程

若子进程先结束:父进程如果没有及时回收子进程,则子进程变成僵尸进程

父子进程区分

如果pid==0 表示子进程

如果pid > 0 表示父进程

如果pid < 0表示出错

进程的退出(kill是直接杀掉进程)

void  exit(int  status);

结束当前的进程并将status返回   exit结束进程时会刷新(流)缓冲区

return 和exit的区别

main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。

进程的回收(wait 、 waitpid)

1)pid_t wait(int *status);  成功时返回回收的子进程的进程号;失败时返回EOF

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

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

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

status为NULL表示直接释放子进程PCB,不接受返回值

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

pid:用于指定回收那个子进程或任意子进程(参数PID介绍如下)

option指定回收,0或WHOHANG

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

参数PID

pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

Exec函数族(shell的调用执行原理就是采用exec)

进程调用exec函数执行某个程序,进程当前内容被指定的程序替换,实现让父子进程执行不同的程序。

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

Exec使用方法(execl、exexlp)

int execl(const char *path, const char *arg, …NULL);  //最后一个参数必须用空指针(NULL)作结束

int execlp(const char *file, const char *arg, …NULL);  //最后一个参数必须用空指针(NULL)作结束

成功时执行指定的程序;失败时返回EOF

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

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

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

两个函数区别execlp不需要写文件名全路径,在PATH查找

守护进程

通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

举例:

http 服务的守护进程叫 httpd,mysql 服务的守护进程叫 mysqld。

创建守护进程

方式1:创建子进程、父进程退出

子进程变cheng1孤儿,被init进程收养

子进程在后台运行

方式2:使用nohub命令

GDB调试多进程程序

  1. gcc –g 一定要进行-g编译
  2. gdb +程序名  进入GDB运行环境
  3. start进入单步运行,run运行整个代码(若没有打断点)
  4. n是下一步

set follow-fork-mode child     设置GDB调试子进程

set follow-fork-mode parent   设置GDB调试父进程

set detach-on-fork  on/off    设置GDB跟踪调试单个进程或多个

info inferiors 显示GDB调试进程

inferiors 进程序号   用于切换GDB调试的进程

线程

进程在切换时系统开销大、很多操作系统引入·轻量级进程LWP

同一进程中的1共享相同地址空间、Linux不区分进程、线程

Linux内核没有提供线程操作,而是通过外挂的pthread线程库来实现线程

Pthread线程库提供如下基本操作

创建线程、回收线程、结束线程

同步和互斥机制:信号量、互斥锁

线程创建  

#include  <pthread.h>    /./线程头文件

int  pthread_create(    pthread_t *thread,

const pthread_attr_t *attr,   //线程属性

void *(*routine)(void *),  //回调函数

void *arg);   //回调函数参数

thread线程对象

attr 线程属性、NULL代表默认属性

routine线程执行的函数

arg 传递给routine的参数 ,参数是void * ,注意传递参数格式,

线程会随着主进程的退出而退出

线程的退出:void  pthread_exit(void *retval); 

获取自己的线程ID :pthread_t  pthread_self(void)    

获取进程id :getpid()

线程间参数传递

  1. 通过地址传递参数,注意类型的转换
  2. 值传递,这时候编译器会告警,需要程序员自己保证数据长度正确

线程的回收:pthread_join

int  pthread_join(pthread_t thread, void **retval);

thread表示要回收的线程对象

调用线程阻塞直到thread结束

*retval 接收线程thread返回值

pthread_join是阻塞函数,如果回收的线程没有结束,则一直等待

线程分离pthread_detach

Int pthread_detach(pthread_t thread)

指定该状态,线程主动与主控线程断开关系,线程结束后(不会产生僵尸线程)

线程的取消(随时杀掉一个线程)

Int pthread_cancel (pthread_t thread);

注意:线程的取消要有取消点才可以,不是说取消就取消,线程的取消点主要是阻塞的系统调用 (比如sleep就是阻塞的系统调用)

如果没有取消点,需手动设置

Void pthread_testcancel(void)

线程的清理

当线程非正常终止,需要清理一些资源

void pthread_cleanup_push(void (*routine) (void *), void *arg)

void pthread_cleanup_pop(int execute)

注意:

1)必须成对使用,即使pthread_cleanup_pop不会被执行到也必须写上,否则编译错误。2)pthread_cleanup_pop()被执行且参数为0,pthread_cleanup_push回调函数routine不会被执行.

3)pthread_cleanup_push 和pthread_cleanup_pop可以写多对,routine执行顺序正好相反

4)线程内的return 可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。

线程同步和互斥

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

临界区:访问临界资源的代码

互斥锁的创建和销毁

两种方法创建互斥锁:静态方式和动态方式

1)动态方式:

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

其中mutexattr用于指定互斥锁属性,如果为NULL则使用缺省属性。

2)静态方式:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

锁的销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex)

在Linux中,互斥锁并不占用任何资源

申请锁

int  pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex)

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

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

pthread_mutex_trylock 如果无法获得锁,返回EBUSY而不是挂起等待

释放锁

Int pthread_mutex_unlock(pthreas_mutex_t *mutex)

读写锁

1)同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。

2)读写锁出于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。

3)读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁请求会被阻塞,以避免写者长时间的不写锁

  • 初始化一个读写锁   pthread_rwlock_init
  • 读锁定读写锁       pthread_rwlock_rdlock
  • 非阻塞读锁定     pthread_rwlock_tryrdlock
  • 写锁定读写锁       pthread_rwlock_wrlock
  • 非阻塞写锁定       pthread_rwlock_trywrlock
  • 解锁读写锁         pthread_rwlock_unlock
  • 释放读写锁         pthread_rwlock_destroy

死锁

比如十字路口交通堵塞

避免方法:

  1. 锁越少越好,最好使用一把锁
  2. 调整好锁的顺序

上面使用了互斥量保证了一个线程访问临界资源

条件变量

针对生产者与消费者问题,是线程同步一种手段,一般与互斥量一起使用

int pthread_cond_wait(pthread_cond_t *restrict cond,

           pthread_mutex_t *restrict mutex);

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

使用步骤

静态初始化(也可以采用动态初始化)

pthread_cond_t   cond = PTHREAD_COND_INITIALIZER;      //初始化条件变量

pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;  //初始化互斥量

pthread_cond_init(&cond);   //动态初始化

生产资源线程:

pthread_mutex_lock(&mutex);

开始产生资源

pthread_cond_sigal(&cond);    //通知一个消费线程

或者

pthread_cond_broadcast(&cond); //广播通知多个消费线程

pthread_mutex_unlock(&mutex);

消费者线程:

pthread_mutex_lock(&mutex);

while (如果没有资源){   //防止惊群效应

pthread_cond_wait(&cond, &mutex);

}

有资源了,消费资源

pthread_mutex_unlock(&mutex);  

注意:

1 pthread_cond_wait(&cond, &mutex),在没有资源等待是是先unlock 休眠,等资源到了,再lock

所以pthread_cond_wait he pthread_mutex_lock 必须配对使用。

如果pthread_cond_signal或者pthread_cond_broadcast 早于 pthread_cond_wait ,则有可能会丢失信号。

3 pthead_cond_broadcast 信号会被多个线程收到,这叫线程的惊群效应。所以需要加上判断条件while循环。

线程池:可以循环的完成任务的一组线程集合

线程池的基本结构:

1 任务队列,存储需要处理的任务,由工作线程来处理这些任务

2 线程池工作线程,它是任务队列任务的消费者,等待新任务的信号

线程池的实现

#include <pthread.h>

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

1 创建线程池的基本结构

#define POOL_NUM 10

typedef struct Task{   //任务队列结构体

    void *(*func)(void *arg);

    void *arg;

    struct Task *next;

}Task;

typedef struct ThreadPool{  // 线程池结构体

    pthread_mutex_t taskLock;

    pthread_cond_t newTask;

    pthread_t tid[POOL_NUM];

    Task *queue_head;

    int busywork;

}ThreadPool;

void pool_init(){   //线程池初始化

    pool = malloc(sizeof(ThreadPool));  //创建一个线程池结构

    pthread_mutex_init(&pool->taskLock,NULL);  //实现任务队列互斥锁和条件变量的初始化

    pthread_cond_init(&pool->newTask,NULL);      pool->queue_head = NULL;

    pool->busywork=0;

    for(int i=0;i<POOL_NUM;i++){    //创建n个工作线程

        pthread_create(&pool->tid[i],NULL,workThread,NULL);

    }

}

void pool_add_task(int arg){   //线程池添加任务

    Task *newTask;

    pthread_mutex_lock(&pool->taskLock);

    while(pool->busywork>=POOL_NUM){   //判断是否有空闲的工作线程

        pthread_mutex_unlock(&pool->taskLock); 

        usleep(10000);

        pthread_mutex_lock(&pool->taskLock);

    }

    pthread_mutex_unlock(&pool->taskLock);

 

    newTask = malloc(sizeof(Task));

    newTask->func =  realwork;

newTask->arg = arg;

    pthread_mutex_lock(&pool->taskLock);

    Task *member = pool->queue_head;

    if(member==NULL){

        pool->queue_head = newTask;

    }else{

       while(member->next!=NULL){

            member=member->next;

       }

       member->next = newTask;

    }

    pool->busywork++;

    pthread_cond_signal(&pool->newTask);

    pthread_mutex_unlock(&pool->taskLock);

}

void *workThread(void *arg){   //实现一个工作线程

    while(1){

        pthread_mutex_lock(&pool->taskLock);

        pthread_cond_wait(&pool->newTask,&pool->taskLock);

        Task *ptask = pool->queue_head;

        pool->queue_head = pool->queue_head->next;

        pthread_mutex_unlock(&pool->taskLock);

        ptask->func(ptask->arg);

        pool->busywork--;

    }

}

void *realwork(void *arg){

    printf("Finish work %d\n",(int)arg);

}

void pool_destory(){    //线程池销毁

    Task *head;

    while(pool->queue_head!=NULL){

        head = pool->queue_head;

        pool->queue_head = pool->queue_head->next;

        free(head);

    }

    pthread_mutex_destroy(&pool->taskLock);

    pthread_cond_destroy(&pool->newTask);

    free(pool);

}

1创建线程池的基本结构:

任务队列链表

typedef struct Task;

线程池结构体

typedef struct ThreadPool;

  1. 线程池的初始化:

pool_init()

{

创建一个线程池结构

实现任务队列互斥锁和条件变量的初始化

创建n个工作线程

}

  1. 线程池添加任务

   pool_add_task

{

    判断是否有空闲的工作线程

给任务队列添加一个节点

    给工作线程发送信号newtask

}

  1. 实现工作线程

   workThread

{

while(1){

   等待newtask任务信号

   从任务队列中删除节点

   执行任务

}

}

  1. 线程池的销毁

   pool_destory

{

删除任务队列链表所有节点,释放空间

删除所有的互斥锁条件变量

删除线程池,释放空间

}

线程的GDB调试

  1. 显示线程:info thread
  2. 切换线程:thread id
  3. GDB为特定线程设置断点:break location thread id
  4. GDB设置线程锁:set scheduler-locking on/off
  5. On: 其他线程会暂停,可以单独调试一个线程

进程间通信

进程间通信方式

无名管道(pipe)、有名管道(fifo)、信号(signal)、共享内存(mmap)、套接字(socket)

其他古老的通信方式:(System V IPC)

共享内存、消息队列、信号灯集

  1. 无名管道
  1. 只能用于亲缘关系的进程间通信(父子进程,兄弟进程)
  2. 管道通信是单工的,一端读,一端写(程序实现设计好)。
  3. 数据自己读不能自己写
  4. 管道可以用于大于2个进程共享

无名管道创建

#include<unistd.h>

Int pipe(int pfd[2])

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

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

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

无名管道创建时会返回两个文件描述符,分别表示读写管道pfd[0]用于读管道、pfd[1]用于写管道

无名管道的读写特性

对于读管道

  1. 管道中有数据:read返回实际读到的字节数
  2. 管道中无数据:管道写段被全部关闭,read返回0;管道写端没有被完全关闭,read阻塞等待

对于写管道

  1. 管道读端全部被关闭,进程异常终止
  2. 管道读端没有全部关闭:管道被写满了,写阻塞;管道未被写满,write将数据写入,并返回实际写入的字节数

有名管道(fifo):其本质就是队列

有名管道特点:

可以使非亲缘关系的两个进程相互通信

通过路径名来操作,在文件系统中可见,但内容存放在内存中

文件IO来操作有名管道

遵循先进先出的规则

单工读写,双工通信需要建两个管道

有名管道创建

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

内存映射

使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必在调用read,write

共享内存

函数:void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

函数返回值:成功返回创建的映射区首地址,失败返回MAP_FAILED( ((void *) -1) ),设置errno值

addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。

length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。

prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。

flags:由以下几个常值指定:MAP_SHARED(共享的) MAP_PRIVATE(私有的), MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)

fd:表示要映射的文件句柄。如果匿名映射写-1。

offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。

注意事项:

  1. 创建映射区的过程,隐含一次对映射文件的读操作,将文件内容读取到映射区
  2. 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭
  3. 文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误)
  4. 映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误 ,超出映射的内存大小报段错误
  5. mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

Mmap()映射的种类

  1. 基于文件的映射
  2. 匿名映射:适用具有亲缘关系的进程之间

释放内存映射

Munmap函数

int munmap(void *addr, size_t length);

返回值:成功返回0,失败返回-1,并设置errno值。

函数参数

addr:调用mmap函数成功返回的映射区首地址

length:映射区大小(即:mmap函数的第二个参数)

信号机制

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

所有信号的产生及处理全部都是由内核完成的

信号处理方式

  1. 缺省方式:默认的信号行为
  2. 忽略信号:
  3. 捕捉信号:改变信号行为

信号的发送(kill/raise)

int kill(pid_t pid, int signum)

功能:发送信号

参数:

          pid:        > 0:发送信号给指定进程

                        = 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。

                        < -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员。

                        = -1:发送信号给,有权限发送的所有进程。

          signum:待发送的信号

int  raise(int sig);      

给自己发信号,等价于kill(getpid(), signo);

定时器函数

unsigned int alarm(unsigned int seconds);

功能:定时发送SIGALRM给当前进程

参数:              seconds:定时秒数

返回值:上次定时剩余时间。

ualarm (循环发送)

useconds_t ualarm(useconds_t usecs, useconds_t interval);

以useconds为单位,第一个参数为第一次产生时间,第二个参数为间隔产生

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

功能:定时的发送alarm信号

参数:

which:有三个选择    

ITIMER_REAL:以逝去时间递减。发送SIGALRM信号

ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送SIGVTALRM信号

ITIMER_PROF: 进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时间。 发送SIGPROF信号

new_value:  负责设定 timout 时间            

old_value:   存放旧的timeout值,一般指定为NULL

struct itimerval {

struct timeval it_interval;  // 闹钟触发周期

struct timeval it_value;    // 闹钟触发时间

};

struct timeval {

    time_t      tv_sec;         /* seconds */

    suseconds_t tv_usec;        /* microseconds */

};

信号捕捉

信号捕捉过程

  1. 定义新的信号的执行函数
  2. 使用signal/sigaction函数,把自定义的handle和指定的信号相关联

signal函数:

typedef void (*sighandler_t)(int);

sighandler_t  signal(int signum, sighandler_t handler);

功能:捕捉信号执行自定义函数

返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR

参数:

      signo 要设置的信号类型

      handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号; 

系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。

sigaction函数:

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {

    void (*sa_handler)(int);

    void (*sa_sigaction)(int, siginfo_t *, void *);

    sigset_t sa_mask;

    int sa_flags;

    void (*sa_restorer)(void);

}

参数:

signum:处理的信号

act, oldact: 处理信号的新行为和旧的行为,是一个sigaction结构体。

sigaction结构体成员定义如下:

sa_handler: 是一个函数指针,其含义与 signal 函数中的信号处理函数类似

sa_sigaction:另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。

sa_flags参考值如下:

SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数

SA_RESTART:使被信号打断的系统调用自动重新发起。

SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

    SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

re_restorer:是一个已经废弃的数据域

定时函数

Alarm和setitimer

通过信号捕捉定时器函数即可实现定时

子进程结束信号

SIGCHLD:是子进程状态改变发给父进程的

使用SIGCHLD信号实现回收子进程

SIGCHLD的产生条件:

1)子进程终止时

2)子进程接收到SIGSTOP信号停止时

3) 子进程处在停止态,接受到SIGCONT后唤醒时

信号集、信号的阻塞

有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况可以通过阻塞信号实现。

信号的阻塞概念:信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

信号的状态

信号递达(Delivery ):实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获)

信号未决(Pending):从产生到递达之间的状态

信号集操作函数

sigset_t set;  自定义信号集。  是一个32bit  64bit  128bit的数组。

sigemptyset(sigset_t *set);       清空信号集

sigfillset(sigset_t *set);      全部置1

sigaddset(sigset_t *set, int signum);       将一个信号添加到集合中

sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除

sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。

设定对信号集内的信号的处理方式(阻塞或不阻塞)

#include <signal.h>

int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );

参数:

how可选用的值:(注意,不能阻塞SIGKILL和SIGSTOP信号)

SIG_BLOCK :   把参数set中的信号添加到信号屏蔽字中

SIG_UNBLOCK: 从信号屏蔽字中删除参数set中的信号

SIG_SETMASK: 把信号屏蔽字设置为参数set中的信号

若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。

若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

int pause(void);

  进程一直阻塞,直到被信号中断,返回值:-1 并设置errno为EINTR

函数行为:

1如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。

2如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回

3 如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause返回-1。

4 pause收到的信号如果被屏蔽,那么pause就不能被唤醒 

int sigsuspend(const sigset_t *sigmask);

功能:将进程的屏蔽字替换为由参数sigmask给出的信号集,然后挂起进程的执行

参数:

sigmask:希望屏蔽的信号

消息队列

消息队列就是一个消息的列表,用户可以在消息队列中添加消息、读取消息

消息队列可以按照类型来发送、接收消息

消息队列使用步骤

  1. 打开/创建消息队列  msgget
  2. 向消息队列发送消息 msgsnd
  3. 从消息队列接收消息 msgrcv
  4. 控制消息队列 msgctl

发送端

  1. 申请key
  2. 打开/创建消息队列  msgget
  3. 向消息队列发送消息 msgsnd

接收端

  1. 打开/创建消息队列  msgget
  2. 从消息队列接收消息 msgrcv
  3. 控制(删除)消息队列 msgctl

消息的发送

Int msgsnd(int msgid,const void *magp,size_t size,int msgflg)

msgid:消息队列id

msgp:消息缓冲区地址

size:消息正文长度

msgflg:标志位0 或 IPC_NOWAIT

msgflg解释:

0代表当消息队列满时,msgsnd将会阻塞,知道消息能写进消息队列

IPC_NOWAIT:表示当消息队列已满的时候,magsnd函数不等待立即返回

消息的格式

typedef struct

       long msg_type;

       char buf[128];

}msgT;

消息结构必须有long类型的msg_type字段,表示消息的类型

消息长度不包括首类型long

消息的接收

int msgrcv(int msgid, void*msgp,size_t size,long msgtype,int msgflg);

msgid:消息队列id

msgp:消息缓冲区地址

size:接收消息的长度

msgtype:接收消息的类型

msgflg:标志位(0或者IPC_NOWAIT或PC_EXCEPT)

msgflg介绍:

0:阻塞式接收消息

IPC_NOWAIT: 如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG

IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型部位msgtype的消息

msgtype介绍

msgtype=0 : 收到的第一条消息,任意类型

msgtype>0:收到的第一条msg type类型的消息

msgtype<0: 接收类型等于或小于msgtype绝对值的第一个消息

信号灯(也被称为信号量)

信号量代表某一类资源,其值表示系统中该资源的数量,仅仅用来作为消息同步作用

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

初始化

P操作(申请资源) 类似消费者把资源数量-1

V操作(释放资源) 类似生产者把资源数量+1

有三种信号灯

Posix 有名信号灯

Posix 无名信号灯(在linux中只支持线程同步)  //Posix使用必须pthread

System V 信号灯

Posix 有名信号灯和无名信号灯使用:

有名信号灯打开

sem_t *sem_open(const char *name, int oflag)

sem_t *sem_open(const char *name, int oflag,mode_t mode,unsigned int value);

参数:

name:name是给信号灯起的名字

oflag:打开方式

mode:文件权限

value:信号量值

打开的信号灯文件放在/dev/shm位置下

有信号灯关闭

int sem_close(sem_t*sem)

有名信号灯删除

int sem_unlink(const char* name);

信号灯P操作

int sem_wait(sem_t*sem);

获取资源,如果信号量为0,表示此时没有相应资源空闲,那么调用线程就将挂起,直到有空闲资源可以获取。对信号量的值进行减1操作

信号灯V操作

int sem_post(sem_t*sem);

释放资源,如果没有线程阻塞在sem上,表示没有线程等待该资源,此时该函数就对信号量的值进行增1操作。如果至少有一个线程阻塞在该sem上,表示有线程等待资源,信号量为0。

System V 信号灯使用

int semget(key_t key, int nsems, int semflg); //创建/打开信号灯

int semop(int semid, struct sembuf*opsptr,size_t nops);

对信号灯集合中的信号量进程P-V操作

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值