[Linux]Linux_process_thread_note

进程线程笔记

1.1 进程基础

1.1.1 程序和进程

  • 程序:是静态的,没有任何执行的概念。

    • 程序 = 代码 + 数据
  • 进程:是程序的一次动态执行过程,进程是程序执行和资源管理的最小单元,进程是一个独立的可以调度的任务

    • 进程 = 代码 + 数据 + 系统资源[从外部角度看]

    • 进程 = task_struct + 4G(虚拟内存)[从内核角度看]

      • task_struct是PCB(进程管理块)的一种,是Linux系统中描述进程的一个结构体

        • 内容

          1 标识符:pid/tid
          2 状态
          3 优先级
          4 程序计数器
          5 内存指针
          ......
          

1.1.2 进程的标志

  • pid:进程号,是唯一标识进程的符号,是task_struct中的一项成员

    • 获取方法

      getpid();
      
  • ppid:父进程号,是标识当前进程的父进程的符号

    • 获取方法

      getppid();
      

1.1.3 进程的类型

  • 交互进程:给指令就有回应的进程。如ls、ps
  • 批处理进程:是一个进程集合,维护着一个进程的队列,负责按照时间先后顺序启动队列中的进程。如shutdown -h time
  • 守护进程:周期性执行某项任务或等待某个事件发生的进程,它的运行不依赖shell终端,它的生存周期较长,从开机开始运行直到关机结束

1.1.4 进程相关的shell操作指令

ps -aux#查看系统中的进程
ps -axj#详细查看系统中进程
top#实时查看linux系统进程
pstree#查看进程树
kill#关闭一个进程。如kill -9  进程号
nice#以指定优先级运行进程。如nice -20 ./test
renice#改变正在运行的进程的优先级。如renice  19  进程号

1.1.5 进程的状态

  • 运行态:running(R)
    • R 运行状态(running):并不意味着进程一定在运行中,也可以在运行队列中
  • 等待态:正在等待被运行,sleep
    • S 睡眠状态(sleeping):进程在等待事件完成(浅度睡眠,可以被唤醒)
    • D 磁盘睡眠状态(disk sleep):不可中断睡眠(深度睡眠,不可以被唤醒,通常在磁盘写入时发生)
  • 停止态:stopT/Z/X (kill -STOP pid)
    • T 停止状态(stopped):可以通过发送stop信号给进程来停止进程,可以发送SIGCONT信号让进程继续运行。
    • Z 僵尸状态(zombie):子进程退出,父进程还在运行,但是父进程没有读到子进程的退出状态值
    • X 死亡状态(dead):该状态为返回状态,在任务列表中看不到

1.2 进程相关的函数

1.2.1 fork

  • pid_t fork(void)

  • 函数功能:在当前进程下(当前进程是父进程),创建一个子进程

  • 需要的头文件:

    #include <sys/types.h>

    #include <unistd.h>

  • 函数名:fork

  • 函数参数:void

  • 函数返回值:

    0:表示子进程

    大于0的整数(子进程的pid):表示父进程

    -1:出错

1.2.2 exec函数族

execl函数
  • int execl(const char *path, const char *arg, …)

  • 函数功能:用path路径下的进程替换当前进程

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:execl

  • 函数参数:

    path:带路径的可执行文件名

    arg:执行可执行文件需要的指令

    …:以列表形式传参,以NULL结束

  • 函数返回值:

    成功:不会产生返回值

    出错:-1

示例
execle函数
  • int execle(const char *path, const char *arg, …, char *const envp[])

  • 函数功能:用path路径下的进程替换当前进程

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:execle

  • 函数参数:

    path:带路径的可执行文件名

    arg:执行可执行文件需要的指令

    …:以列表形式传参,以NULL结束

    envp:预先定义的存放自定义的环境变量的数组,以NULL结束

  • 函数返回值:

    成功:不会产生返回值

    出错:-1

execlp函数
  • int execlp(const char *file, const char *arg, …)

  • 函数功能:使用环境变量下的可执行文件替换当前进程

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:execlp

  • 函数参数:

    file:环境变量下的可执行文件名,不用加路径

    arg:执行可执行文件需要的指令

    …:以列表形式传参,以NULL结束

  • 函数返回值:

    成功:不会产生返回值

    出错:-1

execv函数
  • int execv(const char *path, char *const argv[])

  • 函数功能:用path路径下的进程替换当前进程

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:execv

  • 函数参数:

    path:带路径的可执行文件名

    argv:预先定义的执行可执行文件需要的指令的数组,以NULL结束

  • 函数返回值:

    成功:不会产生返回值

    出错:-1

execve函数
  • int execve(const char *path, char *const argv[], char *const envp[])

  • 函数功能:用path路径下的进程替换当前进程

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:execve

  • 函数参数:

    path:带路径的可执行文件名

    argv:预先定义的执行可执行文件需要的指令的数组,以NULL结束

    envp:预先定义的存放自定义的环境变量的数组,以NULL结束

  • 函数返回值:

    成功:不会产生返回值

    出错:-1

execvp函数
  • int execvp(const char *file, char *const argv[])

  • 函数功能:使用环境变量下的可执行文件替换当前进程

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:execvp

  • 函数参数:

    file:环境变量下的可执行文件名,不用加路径

    argv:预先定义的执行可执行文件需要的指令的数组,以NULL结束

  • 函数返回值:

    成功:不会产生返回值

    出错:-1

1.2.3 exit/_exit

exit函数
  • void exit(int status)

  • 函数功能:退出当前进程并刷新IO缓存区

  • 需要的头文件:

    #include <stdlib.h>

  • 函数名:exit

  • 函数参数:

    status:0表示正常结束;其他数值表示非正常结束

  • 函数返回值:void

_exit函数
  • void _exit(int status)

  • 函数功能:退出当前进程,不刷新IO缓存

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:_exit

  • 函数参数:

    status:0表示正常结束;其他数值表示非正常结束

  • 函数返回值:void

1.2.4 wait/waitpid

wait函数
  • pid_t wait(int *status)

  • 函数功能:等待任意子进程的退出状态(第一个退出的子进程)

  • 需要的头文件:
    #include <sys/types.h>
    #include <sys/wait.h>

  • 函数名:wait

  • 函数参数:

    status:指向子进程退出状态值的指针

  • 函数返回值:

    成功:返回等到的子进程的PID
    失败:返回-1

waitpid函数
  • pid_t waitpid(pid_t pid, int *status, int options)

  • 函数功能:等待指定子进程的退出状态

  • 需要的头文件:

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

  • 函数名:waitpid

  • 函数参数:

    pid:子进程的PID;

    status:指向子进程退出状态值的指针

    options:模式选择

    ​ 0:将waitpid函数变成阻塞函数,若本次调用没有等到结果,则阻塞当前进程;若等到结果,则返回子进程的PID。
    ​ WNOHANG:将waitpid函数变成非阻塞函数,若本次调用没有等到子进程则退出,waitpid返回0;若等到结果,waitpid返 回子进程的pid。

  • 函数返回值:

    >0:已经结束运行的子进程的进程号

    0:使用选项WNOHANG且子进程没有退出

    -1:出错

2.1 守护进程

2.1.1 守护进程的定义

  • 周期性的执行某项任务或等待某个事件发生的进程,不依赖shell终端,生存周期较长,从开机开始运行,直到关机结束
  • 通常用于服务器,TTY为一个问号"?"

2.1.2 创建一个守护进程的步骤

  • 创建一个子进程,父进程退出(此时子进程是孤儿进程)

    pid=fork(); 
    if(pid>0)
    {
        exit(0);
    }
    
  • 创建新的会话期(使子进程脱离shell终端),使子进程从孤儿进程变为守护进程 (会话期相当于一个终端 一个班级,进程组相当于进程的集合,相当于一个班里的几个组)

    setsid();
    
  • 改变子进程的工作目录为根目录

    chdir("/");
    
  • 取消文件掩码

    umask(0);
    
  • 关闭所有父进程中可能打开的文件描述符(使用gettablesize得到文件描述符的数量)

    close(i);//从i = 3开始关闭,0,1,2是大部分文件可能的文件描述符,可能影响标准输入输出函数的使用
    
创建一个守护进程示例

2.2 线程基础

2.2.1 线程定义

  • 线程是一种轻量级的进程
  • 其与创建它的进程共享一块内存空间
  • 线程和进程一样统一参与操作系统的调度

2.2.2 引入线程的原因

  • 进程间通信系统开销大,需要跨越地址空间
  • 同一进程创建的两个线程间通信系统开销小,不用跨越地址空间

2.2.3 创建线程

使用pthread_create函数

  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

2.2.4 创建线程涉及的函数

pthread_create函数
  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg)//在进程中使用

  • 函数功能:创建一个线程

  • 需要的头文件:

    #include <pthread.h>

  • 函数名:pthread_create

  • 函数参数:

    thread:指向线程id的指针

    attr:指向线程属性的指针,通常给NULL

    (*start_routine) (void *):指向线程执行函数的指针

    arg:传给线程执行函数的入参

  • 函数返回值:

    0:成功

    非0:失败

pthread_join函数
  • int pthread_join(pthread_t thread, void **value_ptr)//在进程中使用

  • 函数功能:阻塞等待线程退出,回收线程退出状态值

  • 需要的头文件:

  • 函数名:pthread_join

  • 函数参数:

    thread:线程ID

    value_ptr:指向线程退出状态值的指针

  • 函数返回值:

    0:成功

    非0:失败

pthread_exit函数
  • int pthread_exit(void *value_ptr)//在线程中使用

  • 函数功能:退出当前线程

  • 需要的头文件:

  • 函数名:pthread_exit

  • 函数参数:

    value_ptr:指向线程退出状态的指针

  • 函数返回值:

    0:成功

    非0:失败

pthread_cancel函数
  • int pthread_cancel(pthread_t thead)//在进程中使用

  • 函数功能:取消线程/关闭线程

  • 需要的头文件:

  • 函数名:pthread_cancel

  • 函数参数:

    thead:线程ID

  • 函数返回值:

    0:成功

    非0:失败

示例

2.3 多线程引入的问题

2.3.1 线程间通信

2.3.1.1 线程间通信的定义
  • 线程间数据交换
2.3.1.2 线程间通信的方法
  • 使用全局变量(在当前文件中定义)

2.3.2 线程同步

2.3.2.1 同步的定义
  • 是指两个对象/事物/任务按照约定好的顺序相互配合完成一件事情
2.3.3.2 线程同步的定义
  • 两个线程按照约定好的顺序相互配合完成一件事情
2.3.3.3 线程同步的实现方法
  • 1 使用无名信号量(二值信号量)

    sem_t  sema;//信号量
    sem_t  semb;//信号量
    
  • 2 使用P操作和V操作函数控制无名信号量

    • P操作
      • sem_wait(&sema)
      • 用法:
        • 当sema > 0时,函数返回,sema–
        • 当sema = 0时,阻塞当前线程
      • 返回值:
        • 成功返回0
        • 出错返回-1,且置errno
    • V操作
      • sem_post(&semb)
      • 用法:
        • 给信号量为0的信号量加一,非阻塞
      • 返回值:
        • 成功返回0
        • 出错返回-1,且置errno
2.3.3.4 线程同步涉及到的函数
sem_init函数
  • int sem_init(sem_t *sem, int pshared, unsigned int value)

  • 函数功能:初始化信号量

  • 需要的头文件:

    #include <semaphore.h>

  • 函数名:sem_init

  • 函数参数:

    sem:信号量对象

    pshared:决定信号量能否在进程间共享,一般为0

    value:信号量初始化值

  • 函数返回值:

    0:初始化成功

    -1:初始化失败

sem_wait函数
  • int sem_wait(sem_t *sem)

  • 函数功能:获取信号量,若不成功则阻塞。当sem > 0时,函数返回,sem–;当sem=0时,阻塞当前线程

  • 需要的头文件:

    #include <semaphore.h>

  • 函数名:sem_wait

  • 函数参数:

    sem:信号量对象

  • 函数返回值:

    0:表示成功

    -1:表示出现错误,信号量的值保持不变

sem_post函数
  • int sem_post(sem_t *sem)

  • 函数功能:释放信号量。给信号量为0的信号量+1,sem++

  • 需要的头文件:

    #include <semaphore.h>

  • 函数名:sem_post

  • 函数参数:

    sem:信号量对象

  • 函数返回值:

    0:表示成功

    -1:表示出现错误,信号量的值保持不变

//sem_wait和sem_post两个函数配合使用示例
while(1)
{
	sem_wait(&sem_printf);//消耗一个资源,sem_printf--
	printf("printf_msgbuf :***********\n");
	printf("printf_msgbuf :%s\n",msg->buf);
	sem_post(&sem_printf);//得到一个资源,sem_printf++
}

2.3.3 线程互斥

2.3.3.1 引入
  • 两个线程可以使用同一个线程执行函数,若在该线程执行函数中定义了一个公共的buf,两个线程同时使用公共的buf,就会出现资源的竞争访问问题,会引起数据存放混乱。
2.3.3.2 解决竞态的方法
  • 加互斥锁
2.3.3.3 线程互斥涉及到的函数
pthread_mutex_init函数
  • int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr)

  • 函数功能:初始化互斥锁

  • 需要的头文件:#include <pthread.h>

  • 函数名:pthread_mutex_init

  • 函数参数:

    mutex:指向互斥锁对象的指针

    attr:互斥锁属性,一般给NULL

  • 函数返回值:

    0:初始化成功

    非0:初始化失败

pthread_mutex_lock函数
  • int pthread_mutex_lock(pthread_mutex_t *mutex)

  • 函数功能:给公共资源加锁

  • 需要的头文件:#include <pthread.h>

  • 函数名:pthread_mutex_lock

  • 函数参数:

    mutex:指向互斥锁对象的指针

  • 函数返回值:

    0:加锁成功

    非0:加锁失败

pthread_mutex_unlock函数
  • int pthread_mutex_unlock(pthread_mutex_t *mutex)

  • 函数功能:给公共资源解锁

  • 需要的头文件:#include <pthread.h>

  • 函数名:pthread_mutex_unlock

  • 函数参数:

    mutex:指向互斥锁对象的指针

  • 函数返回值:

    0:解锁成功

    非0:解锁失败

示例

3.1 进程间通信(一台主机间通信)

  • 通信领域(应用层)概念:
    • 单工通信:传输方向单向
    • 半双工通信:传输方向双向,但同一时刻只能一个方向
    • 全双工通信:传输方向双向,可以双向同时传送

3.1.1 无名管道

3.1.1.1 无名管道的定义
  • 无名管道是一种管道文件
  • 用于有亲缘关系的进程间的通信
  • 有固定的读端和写端,通常当单工使用(本质上是半双工,但通信时刻不好把握),需要一个管道文件
  • 无法通过“ls -la”查看该文件
  • 父进程创建无名管道,子进程继承无名管道,所以父子进程可操作同一个管道
3.1.1.2 无名管道的创建方法
  • 使用pipe函数创建无名管道

注意:

  • 只有在管道读端存在时,向管道写入数据才有意义
3.1.1.3 使用有名管道实现通信涉及到的函数
pipe函数
  • int pipe(int fd[2])

  • 函数功能:创建无名管道并打开管道,返回fd[0]和fd[1]

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:pipe

  • 函数参数:

    fd:读写操纵数组。(fd[0]:操作管道固定的读端;fd[1]:操作管道固定的写端)

  • 函数返回值:

    0:创建无名管道成功

    -1:创建无名管道失败

3.1.1.4 使用无名管道实现通信示例
#include<stdio.h>
#include <unistd.h>
#include <string.h>

#define BUF_SIZE 20

int main()
{
	//pipe
	int fd[2]={0};
	if(0!=pipe(fd))
	{
		printf("create pipe error!----------------\r\n");
		return -1;
	}
	printf("1:create pipe ok!---------------\r\n");
	//fork
	pid_t pid=fork();
	if(-1==pid)
	{
		printf("fork error!--------------\r\n");
		return -1;
	}
	char buf[BUF_SIZE]={0};
	//pid==0
	if(0==pid)
	{
		//child process
		close(fd[0]);
		printf("child process!-------------\r\n");
		strcpy(buf,"child to parent");
		write(fd[1],buf,BUF_SIZE);
	}
	else //pid>0
	{
		//parent process
		printf("parent process!------------\r\n");
		close(fd[1]);
		read(fd[0],buf,BUF_SIZE);
		printf("read data:%s\r\n",buf);
	}
	return 0;
}

3.1.2 有名管道

3.1.2.1 有名管道的定义
  • 有名管道是一种管道文件
  • 用于任意数量进程间的通信
  • 没有固定的读端和写端,可以用于单工通信和双工通信,用于单工通信需要一个管道文件、两个进程;用于双工通信需要两个管道文件、两个进程
  • 可以通过“ls -la”查看该文件;但文件数据大小为0,因为管道中的数据只存在于内存中,只有存放在硬盘上的文件可以看到具体的文件数据大小
3.1.2.2 有名管道的创建方法
  • 使用mkfifo函数创建有名管道

注意:

  • 只有在管道读端存在时,向管道写入数据才有意义
3.1.2.3 使用有名管道实现通信涉及到的函数
mkfifo函数
  • int mkfifo(const char *filename,mode_t mode)

  • 函数功能:创建有名管道

  • 需要的头文件:
    #include <sys/types.h>
    #include <sys/stat.h>

  • 函数名:mkfifo

  • 函数参数:

    filename:有名管道文件的名称,通常为字符串形式

    mode:文件权限(参见文件IO中的文件权限介绍)

  • 函数返回值:

    0:创建有名管道成功

    -1:创建有名管道失败

fgets函数
  • char *fgets(char *s, int size, FILE *stream)

  • 函数功能:从文件流指针获取字符串

  • 需要的头文件:
    #include <stdio.h>

  • 函数名:fgets

  • 函数参数:

    s:存储字符的内存首地址

    size:获取字符的的数量

    stream:文件流指针

  • 函数返回值:

    s:获取成功返回存储字符的内存首地址

    NULL:当发生错误或者没有字符可获取时返回NULL

3.1.2.4 使用有名管道实现通信示例
/*
有名管道实现单工通信,需要一个有名管道文件(A---->B)
A进程:
1 mkfifo--->创建一个有名管道文件fifo
2 open----->打开有名管道
3 write
4 close
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 30
int main()
{
    //mkfifo
    int ret = -1;
    ret = mkfifo("fifo",0666);
    if(0 > ret && errno != EEXIST)//创建文件失败并且系统中没有fifo文件
    {
        printf("create fifp error\r\n");
        return -1;
    }
    printf("1 mkfifioo------k\r\n");
    //open
    int fd = open("fifo",O_WRONLY,0666);//只能使用系统调用函数open打开fifo文件,不能使用标准库函数fopen
    if(-1 == fd)
    {
        return -1;
    }
    printf("2 openfifo  ok!---\r\n ");
    //write
    char buf[SIZE] = {0};
    strcpy(buf,"hellowoeld");
    write(fd,buf,SIZE);
    //close
    close(fd);
	return 0;
}

/*
B进程:
1 mkfifo--->找到管道文件fifo(若已知已经创建过管道文件,该步可省略)
2 open
3 read
4 close
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 30
int main()
{
    //mkfifo
    int ret = -1;
    ret = mkfifo("fifo",0666);
    if(0 > ret && errno != EEXIST)//创建文件失败并且系统中没有fifo文件
    {
        printf("create fifp error\r\n");
        return -1;
    }
    printf("1 mkfifioo------k\r\n");
    //open
    int fd = open("fifo",O_RDONLY,0666);
    if(-1 == fd)
    {
        printf("open fifp error\r\n");
        return -1;
    }
    //read
    char buf[SIZE] = {0};
    read(fd,buf,SIZE);
    printf("b read data:%s\r\n",buf);
    //close
    close(fd);
	return 0;
}

/*
有名管道实现双工通信,需要两个有名管道文件(A<-->B)
A进程
1 mkfifo-->fifoa
2 mkfifo-->fifob
3 open---->fifoa-->write
4 open---->fifob-->read
5 while
	{
    	//write
        write-->fifoa
        //read
        read--->fifob
     }
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 30
int main()
{
    //1 mkfifo fifoa
    int ret = -1;
    ret = mkfifo("fifoa",0666);
    if(0 > ret && errno != EEXIST)//创建文件失败并且系统中没有fifo文件
    {
        printf("create fifo a error\r\n");
        return -1;
    }
    printf("1 mkfifio a----o--k\r\n");
    //2 mkfifob 
    ret = mkfifo("fifob",0666);
    if(0 > ret && errno != EEXIST)//创建文件失败并且系统中没有fifo文件
    {
        printf("create fifo b error\r\n");
        return -1;
    }
    printf("2 mkfifio b----o--k\r\n");
    //3 open fifoa
    int fdw = open("fifoa",O_WRONLY,0666);
    if(-1 == fdw)
    {
        return -1;
    }
    printf("3  open fifoaok-------\r\n");
    //4 open fifob
    int fdr = open("fifob",O_RDONLY,0666);
    if(-1 == fdr)
    {
        return -1;
    }
    printf("4 open fifobok-------\r\n");
    //5 6 write read
    char buf[SIZE] = {0};
    while(1)
    {
        //write
        memset(buf,0,SIZE);
        fgets(buf,SIZE,stdin);
        write(fdw,buf,SIZE);
        //read
        memset(buf,0,SIZE);
        read(fdr,buf,SIZE);
        printf("b--->a:%s\r\n",buf);
    }
    //close
    close(fdw);
    close(fdr);
	return 0;
}

/*
B进程
1 mkfifo-->fifoa(若已知已经创建过管道文件,该步可省略)
2 mkfifo-->fifob
3 open---->fifoa-->writeread
4 open---->fifob-->write
5 while
	{
        //read
        read--->fifob
    	//write
        write-->fifoa
     }
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 30
int main()
{
    //1 mkfifo fifoa
    int ret = -1;
    ret = mkfifo("fifoa",0666);
    if(0 > ret && errno != EEXIST)//创建文件失败并且系统中没有fifo文件
    {
        printf("create fifo a error\r\n");
        return -1;
    }
    printf("1 mkfifio a----o--k\r\n");
    //2 mkfifob 
    ret = mkfifo("fifob",0666);
    if(0 > ret && errno != EEXIST)//创建文件失败并且系统中没有fifo文件
    {
        printf("create fifo b error\r\n");
        return -1;
    }
    printf("2 mkfifio b----o--k\r\n");
    //3 open fifoa
    int fdr = open("fifoa",O_RDONLY,0666);
    if(-1 == fdr)
    {
        return -1;
    }
    printf("3  open fifoaok-------\r\n");
    //4 open fifob
    int fdw = open("fifob",O_WRONLY,0666);
    if(-1 == fdw)
    {
        return -1;
    }
    printf("4 open fifobok-------\r\n");
    //5 6 write read
    char buf[SIZE] = {0};
    while(1)
    {
        //read
        memset(buf,0,SIZE);
        read(fdr,buf,SIZE);
        printf("a--->b:%s\r\n",buf);
        //write
        memset(buf,0,SIZE);
        fgets(buf,SIZE,stdin);
        write(fdw,buf,SIZE);
    }
    //close
    close(fdw);
    close(fdr);
	return 0;
}

3.1.3 信号通信

  • 异步通信:通信中接收方并不知道信号(数据)何时会到达,当前进程一直准备接收发送方发送的数据,同时也在做自己的事情,一旦信号(数据)到达,马上接收处理,所以是非阻塞通信模式

  • 同步通信:发送方发送数据,接收方同步接收数据,双方需要在很短的时间内完成数据交换,否则会造成一方阻塞,所以是阻塞通信模式

  • 中断:指在程序的执行过程中插入了另外一段程序的执行过程,该段程序执行结束后,CPU回到中断点继续执行原来的程序

3.1.3.1 信号通信的定义
  • 信号通信是在软件层面上对中断机制的一种模拟,是一种异步通信方式,使用信号通信可以实现用户进程和内核进程之间的通信
3.1.3.2 信号来源
  • Linux内核提供了64个信号,使用“kill -l”shell命令可以查看Linux内核中提供的64个信号,每一个信号都对应一个默认处理动作

    例如:

    • 用户点击关闭终端按钮:SIGHUP-------------->终止终端运行的进程
    • 用户键入ctrl+c:SIGINT--------------------------->终止当前进程
    • 用户键入shell命令“kill -9 1000”:SIGKILL—>终止进程1000
    • 用户对SIGKILL和SIGSTOP这两个信号不能忽略

    部分常用信号:

    信号名称默认处理动作
    SIGABORT进程停止运行
    SIGALRM警告钟
    SIGFPE浮点运算例外
    SIGHUP系统挂断
    SIGILL非法指令
    SIGINT终端中断
    SIGKILL停止进程(此信号不能被忽略或捕获)
    SIGPIPE向没有读者的管道
    SIGSEGV无效内存段访问
    SIGQUIT终端退出ctrl+|
    SIGTERM正常终止
    SIGUSR1用户定义信号1
    SIGUSR2用户定义信号2
    SIGCHLD子进程已经停止或退出
    SIGCONT如果被停止则继续执行
    SIGSTOP停止执行
    SIGTSTP终端停止信号
    SIGTOUT后台进程请求进行写操作
    SIGTTIN后台进程请求进行读操作
3.1.3.3 使用信号实现通信涉及到的函数
kill函数
  • int kill(pid_t pid, int sig)

  • 函数功能:给进程或进程组发送信号

  • 需要的头文件:

    #include <sys/types.h>

    #include <signal.h>

  • 函数名:kill

  • 函数参数:

    pid:

    1. 正数:发送信号给进程号为pid的进程
    2. 0:信号被发送到所有和当前进程在同一个进程组的进程
    3. -1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
    4. <-1:信号发送给进程组号为-pid的每一个进程

    sig:信号类型

  • 函数返回值:

    0:发送成功

    -1:发送出错

raise函数
  • int raise(int sig)

  • 函数功能:让进程向自身发送信号

  • 需要的头文件:

    #include <sys/types.h>

    #include <signal.h>

  • 函数名:raise

  • 函数参数:

    sig:信号类型

  • 函数返回值:

    0:发送成功

    -1:发送出错

pause函数
  • int pause(void)

  • 函数功能:挂起当前进程(避免该进程执行剩余代码段,导致该进程结束,对方进程无法与该进程通信),并等待一个信号的到来,可以捕获信号

  • 需要的头文件:

    #include <unistd.h>

  • 函数名:pause

  • 函数参数:

    void

  • 函数返回值:

    -1:pause函数只有等到一个信号的到来才会返回,并且返回值始终为-1,若未等到信号的到来,阻塞

alarm函数
  • unsigned int alarm(unsigned int seconds)

  • 函数功能:该函数是一个闹铃函数,它可以在进程中设置一个定时器,当定时时间到时,内核就向当前进程发送SIGALRM(终止当前进程)信号。(默认动作可修改)

  • 需要的头文件:
    #include <unistd.h>

  • 函数名:alarm

  • 函数参数:

    seconds:秒数

  • 函数返回值:

    0:若参数为0,则返回值为0;若该定时器前没有其他定时器,则返回值也为0

    大于0的整数:若该定时器前还有别的定时器,返回值是上一个定时器被打断计时后剩余的秒数(若有两个定时器,在没有sleep函数打断的情况下,后一个定时器会把第一个定时器打断,定时时间由第二个定时器的秒数决定,此时被打断的定时器返回值为0,第二个定时器的返回值是第一个定时器被打断计时后剩余的秒数)

signal函数
  • sighandler_t signal(int signum, sighandler_t handler)

  • 函数功能:修改信号对应的默认处理动作,signum会自动给handler传送信号编号

  • 需要的头文件:

    #include <signal.h>

  • 函数名:signal

  • 函数参数:

    signum:信号代码,它可以取除了SIGKILL和SIGSTOP外的任何一种信号

    handler:

    typedef void (*sighandler_t)(int):入参int,返回值void

      1. 信号对应的新的处理动作函数	
      2. SIG_IGN:忽略该信号,使该信号失效
      3. SIG_DFL:恢复对信号的系统默认处理,使该信号保持系统默认的作用
    
  • 函数返回值:

    成功:返回以前的信号处理函数

    出错:-1

atoi函数
  • int atoi(const char *nptr)

  • 函数功能:将指向存储字符串首地址的指针指向的字符型数据转换为整型数据

  • 需要的头文件:

    #include <stdlib.h>

  • 函数名:atoi

  • 函数参数:

    nptr:指向字符串首地址的指针

  • 函数返回值:

    >0的整数:当转换成功时,返回转换后的整数

    0:当转换失败时,返回0

3.1.3.4 使用信号实现通信示例
  • 在Linux系统中,进程对信号的处理方式:

    1. 捕获信号

    2. 采用默认处理动作(可更改)

    3. 忽略信号

/*
通过信号实现A进程和B进程的通信
条件:
1 信号:SIGUSR1 SIGUSR2
2 信号的发送方发送信号的方式:使用kill或者raise函数
3 信号的接收方捕获信号的方式:使用pause函数

A进程:发送信号
1 使用main函数传参,从而获取对方进程的进程号pid 
2 使用kill(pid,SIGUSR1)发送SIGUSR1信号
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc, char *argv[])
{
    if(2 != argc || NULL == argv[1])
    {
        return -1;
    }
    int pid = atoi(argv[1]);
    if(0 >= pid)
    {
        return -1;
    }
    kill(pid,SIGUSR2);
	return 0;
}

/*
B进程:接收信号并处理
1 输出进程号
2 使用signal(SIGUSR1,Func)修改信号的默认处理动作
3 使用pause挂起B进程,延长B进程的生存周期,从而可以捕获到一个信号
*/
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void Func(int signo)
{
    if(signo == SIGUSR1)
    {
        printf("user1 signal-------\r\n");
    }
    else
    {
        printf("other-----------\r\n");
    }

}
int main()
{
    printf("pid = %d\r\n",getpid());
    signal(SIGUSR1,Func);
    signal(SIGUSR2,Func);
    while(1)
    {
        pause();
    }
	return 0;
}

  • 进程间通信

4.1 共享内存

ftok

shmget

shmat

shmdt

shmctl

4.2 消息队列

ftok

msgget

msgsnd

msgrcv

msgctl

4.3 信号灯

sem_open

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值