进程和线程详解

1. 进程相关概念

进程是一个程序的一次执行的过程

每一个进程都分配一个虚拟的4G内存

32位 0-4G 64位0-8G

0-3G : 用户空间
3G-4G : 内核空间

1.1 进程和程序的区别

程序:存放在硬盘上的指令和命令的有序集合,是静态的;

程序的组成:代码段、用户数据段(静态区、全局变量、malloc开辟的空间)<<<程序运行之前

进程:程序的一次执行过程(创建、调度、消亡),是动态的;

进程的组成:代码段、用户数据段、系统数据段(进程控制块(pcb)、pc寄存器、堆栈)

进程控制块(pcb):

            进程ID号---PID;
            用户信息(用户名和用户组名);
            进程状态(ctrl c结束;ctrl z暂停挂起,用信号进行唤醒);
            优先级(优先级值越小,等级越高,抢占cpu效率越高);
            文件描述符表(进程运行结束时,系统会自动关闭文件描述符表中的文件);
pc寄存器(程序计数器):

			存放程序下一条指令的地址;
堆栈---栈:
		每个进程都有自己的栈,进程运行时才会开辟;	

进程是系统资源管理的最小单位;

1.2 进程相关指令

ps -ef:显示系统中正在运行的进程;

UID:用户名
PPID:父进程号
TTY:终端运行程序,包括守护进程

ps aux:显示系统中正在运行的进程详细信息(包含运行状态);

STAT:进程状态
S睡眠状态
R运行状态
+前台进程(终端可以看见)

top:动态显示系统中正在运行的进程;

1.3 进程的类型
1.3.1 交互进程

(与shell终端相关,可以输入可以输出,既可以在前台运行,也可以在后台运行)

结束进程:kill

kill -l : 查看信号种类
kill -9 PID : 杀死进程
getpid();	//得到当前进程pid号
1.3.2 批处理进程(运维)

将一系列进程放在一个工作队列中,按顺序执行。

1.3.3 守护进程

跟终端无关,一直在后台运行,一般服务器会选择守护进程实现。

1.4 进程的运行状态

1、R—运行态:正在运行的程序或者准备运行的程序;

2、等待态:等待某种资源或者等待资源调度;

分为可中端和不可中断;

区别在于是否能被信号打端;
S---可中断等待态:如果进程收到信号会醒来
D---不可中断等待态:如果进程收到信号不会醒来

3、T—暂停态:暂停运行,需要使用某种信号将正在暂停的进程唤醒;

4、Z—死亡态(僵尸态):进程结束后,没有主动回收资源,就会进入僵尸态;

请添加图片描述

1.5 优先级(NI)

优先级决定了进程执行的先后顺序;

通过指令top可以查看优先级,NI表示进程的优先级;

优先级的取值范围:-20~19 默认优先级为0

NI值越小的优先级越高

修改优先级指令

修改准备运行的进程优先级
nice -n 指定优先级 ./a.out
nice -n 2 ./a.out//普通用户只能修改0-19
sudo nice -n -2 ./a.out

修改正在运行的进程优先级
renice -n 指定优先级 进程PID
1.6 前后台进程切换

./a.out & 进程后台运行;

ctrl + z:将正在运行的前台进程在后台挂起 — 处于暂停态;

bg + 任务号:将后台挂起的指定进程唤醒;(jobs可以在终端显示任务号)

fg + 任务号:将运行的后台放在前台运行。

1.7 进程相关的函数

1、创建进程—fork()

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

pid_t fork(void);

返回值:
    成功:父进程返回进程的pid;
		 子进程返回0;

    失败:父进程返回-1;

示例

pid_t pid = fork();
//pid t = pid;
//pid = fork();
if(pid < 0)
{
    perror("fork");
    
}
fork();//会创建4个进程
//子进程执行
if(pid == 0)
{
    printf("pid = %d\n",getpid());//获取pid号
}
else//父进程执行
{
    fork()//会创建3个进程
}

一般来说父进程先运行(不绝对)    

2、父子进程

返回值、形参—左值 &—引用 右值—&—取地址

c++ 默认禁止临时变量产生

子进程会拷贝父进程的几乎所有数据,是相互独立的地址空间;

写操作时拷贝:通过允许父子进程可访问相同的物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数据时才拷贝父进程。

父进程先于子进程结束:

子进程变成孤儿进程,由前台变为后台进程,统一由init(1号进程)进程收养;

子进程先于父进程结束

子进程结束后,一般父进程结束时对子进程进行自动回收,如果没有回收,则子进程变为僵尸进程;

子进程执行时机:fork函数的下一个语句;

3、结束进程 exit()/_exit()

exit()

	#include <stdlib.h>
	void exit(int status);

参数:
	status:结束进程时返回的信息     //返回信息由父进程wait()接收,可以随意填写一个整数
//结束进程时,如果缓冲区中有数据,会刷新缓冲区;

_exit()

	#include <stdlib.h>
	void _exit(int status);
//结束进程时,如果缓冲区中有数据,不会进行刷新;

4、等待回收子进程 wait()/waitpid()

wait()

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

pid_t wait(int *wstatus);

参数:
    wstatus:表示存放子进程结束时返回的信息;
        	如果不想接收子进程返回的信息,可以填NULLWIFEXITED(wstatus) --- 判断子进程是否是正常退出,返回1表示正常退出,0表示非正常退出;
WEXITSTATUS(wstatus) --- 得到exit结束时返回的值,非正常退出(kill -9打断)时返回0WIFSIGNALED(wstatus) --- 判断子进程是否为信号打断,返回1表示被信号打断;
WTERMSIG(wstatus) --- 返回信号的编号

返回值:
    成功:返回回收子进程的ID号;
    失败:返回-1//一个wait函数只能接收一个子进程;多个子进程结束时,谁先结束,先回收谁;
        
pid_t waitpid(pid_t pid,int *wstatus,int options);        
参数:
    pid:选择等待回收的子进程ID
    options:
		0 ---  表示以阻塞方式等待回收子进程
        WNOHANG --- 表示以f阻塞方式等待回收子进程

举例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        exit(-1);
    }

    if(pid == 0)
    {
        int n = 10;
        while(n--)
        {
            printf("child---%d\n",getpid());
            sleep(1);
        }
    }
    else
    {
        pid_t id;
        int n = 5;
        while(n--)
        {
            printf("parent---%d\n",getpid());
            sleep(1);
        }
        int status;
        id = wait(&status);
        printf("id = %d true = %d ret = %d signal = %d signal_val = %d\n",id,WIFEXITED(status),WEXITSTATUS(status),WIFSIGNALED(status),WTERMSIG(status)
);
    }
    return 0;
}


/***********运行结果**************/
kill -9 3441

parent---3440
child---3441
parent---3440
child---3441
parent---3440
child---3441
parent---3440
child---3441
parent---3440
child---3441
child---3441
id = 3441 true = 0 ret = 0 signal = 1 signal_val = 9
二、进程
1、exec函数族

在一个进程中调用另外的进程

list:列表;
p:PATH;


#include <unistd.h>
int execl(const char *pathname, const char *arg, ... ,NULL);
参数:
    pathname:要执行的程序名(要加绝对路径);
    arg:执行程序的命令行参数;
    省略号表示后续的命令行参数,最后以NULL做结尾;
	eg:
		execl("/bin/ls","ls","-l",NULL);//不能写为"ls -l",这样只执行ls;
		execl("/home/hqyj/IO/day1/hello","./hello",NULL);
//gcc hello.c -o hello

int execlp(const char *file, const char *arg, ... ,NULL);
参数:
    file:文件;
    eg:
		execl("hello","./hello",NULL);
//hello要通过sudo cp hello /bin放到bin目录中 或者改变环境变量


int execv(const char *path, char *const argv[]);
//定义一个指针数组char *const argv[]将*path路径后面的所有命令都放在指针数组中
//指针数组中最后的元素必须是NULL
char *arr[] = {"ls","-l",NULL};
execv("/bin/ls",arr);


int execvp(const char *file, char *const argv[]);
char *arr[] = {"ls","-l",NULL};
execvp("ls",arr);
2、守护进程

在后台一直运行,周期性地等待某些事件,与终端无关,一般用于服务器;

创建父进程,fork子进程,退出父进程,子进程就为守护进程;

进程组:创建一个进程时,就有一个进程组;跟创建进程具有亲缘关系地进程都属于该组

会话:打开一个终端时,就创建一个会话,会话由一个或者多个进程组组成;

守护进程创建流程:

1、创建一个子进程,父进程退出 — fork();

2、脱离原本会话 — setsid();

3、更改工作路径(可省略)为 /temp — chdir(/temp) //更改文件路径;

4、修改文件权限掩码(可省略) — umask();

文件权限掩码:指屏蔽掉文件权限中的对应位。例如,有个文件权限掩码为050,它就屏蔽掉了文件组拥有者的可读与可执行权限。由于 fork创建的子进程继承了父进程的文件掩码,这给子进程使用文件带来了麻烦,因此将文件掩码设置为0(即不屏蔽任何权限)可以增强守护进程的灵活性

5、关闭所有的文件描述符 — getdtablesize() //获取文件描述符最大值;

while(1)

{

​ 周期性执行的代码;

}

练习:

创建一个守护进程,每隔1s向xx.log日志文件中写入当前时间工作路径设置为 /tmp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char *argv[])
{
    pid_t pid = fork();			//创建子进程
    if(pid < 0)
    {
        perror("fork");
        exit(-1);
    }
    if(pid > 0)
    {
        exit(0);				//父进程退出
    }

    setsid();					//脱离原本会话
    
    chdir("/tmp");				//修改工作路径,防止文件误删

    umask(0);					//修改文件权限掩码为0000,守护进程创建的文件什么权限,就是什么权限

    for(int i = 0; i<getdtablesize(); i++)		//关闭所有父进程继承下来的文件描述符
    {
        close(i);
    }

    FILE *fp = fopen("time.log","a+");
    if(NULL == fp)
    {
        perror("fopen");
        exit(-1);
    }

    while(1)
    {
        time_t t;
        time(&t);		//获取当前时间(秒数),利用ctime转换成字符串格式
        fprintf(fp,"%s", ctime(&t)); //打开一个文件默认是全缓存,缓存区满才会输出
        fflush(fp);		//刷新缓冲区
        sleep(2);
    }
	return 0;
}
三、线程
1、线程基础

多进程的实现

程序在内存上运行,数据在虚拟内存上,与物理内存存在映射关系;
//34位操作系统4G虚拟内存,指针4字节;64位有8G,指针8字节;
//每个进程都有虚拟内存空间;
//系统为每个进程创建一个task struct来描述该进程,该结构体中包含了一个指针,指向该进程的虚拟地址空间映射表;
//每个进程都可以用 task struct + 8G虚拟内存来表示;
CPU与cache高速缓存进行数据交互,效率比直接从内存中读取数据要快很多;
    
进程的物理地址相互独立,在进行进程切换时,cache高速缓冲区需要频繁刷新,消耗资源巨大;

轻量级进程 — 线程:必须通过进程创建;

线程是最小的任务调度单位;

多线程的实现

多个线程共享同一个地址空间,减少cache刷新频率;
如果创建线程的进程提前结束,那么进程创建的所有线程
查看线程指令:ps -eLf --- LWP线程号

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

1、可执行的指令

2、静态数据(全局变量,static修饰变量)

静态全局区:全局变量,static修饰变量;
栈区:局部变量,函数参数返回值;
//进程间的数据交互只能在内核空间来实现

3、进程中打开的文件描述符

4、信号处理函数

5、当前工作目录

6、用户ID与用户组ID

线程间私有的数据

1、线程ID

2、PC

3、状态、优先级

4、堆栈 == 栈

2、线程相关接口函数
1.创建线程 – pthread_create()
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                  void *(*start_routine) (void *), void *arg);
参数:
    thread:线程对象,通过 pthread_t thread1 自定义;
    attr:线程属性---结合/分离  默认NULL;
    start_rountine:线程处理函数,该函数执行对应线程代码;//函数指针,传han
    arg:线程处理函数的参数,如果没有参数,填NULL;

返回值:
    成功返回0;
    失败,返回错误号;

必须链接库函数 gcc 1.c -pthread

include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

void *func(void *b);
int a = 0;

int main(int argc, char *argv[])
{
    int arg = 10;
    pthread_t thread1;
    if(0 != pthread_create(&thread1, NULL, func, &arg))
    {
       perror("pthread_create");
       exit(-1);
    }

    while(1)
    {
        printf("^^^^^^^^^^ a = %d ^^^^^^^^^^^^\n",a++);
        sleep(1);
    }


    return 0;
}


void *func(void *b)//void *指针要使用必须进行强转
{
    int num = *((int *)b);//地址取内容之前需要进行强转,b转为int *;

    while(1)
    {
        printf("*** a = %d **** num = %d *****\n",a++,num);
        sleep(1);
    }
}

/*********运行结果************/
^^^^^^^^^^ a = 0 ^^^^^^^^^^^^
*** a = 1 **** num = 10 *****
^^^^^^^^^^ a = 2 ^^^^^^^^^^^^
*** a = 3 **** num = 10 *****
^^^^^^^^^^ a = 4 ^^^^^^^^^^^^

需要传递多个参数时,封装为结构体,把结构体的地址传递进来即可;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

void *func(void *b);
int a = 0;

struct std
{
    char name[50];
    int age;
    char sex;
};

int main(int argc, char *argv[])
{
    int arg = 10;
    struct std std1 = {"xiaoming",18,'m'};

    pthread_t thread1;
    if(0 != pthread_create(&thread1, NULL, func, &std1))
    {
       perror("pthread_create");
       exit(-1);
    }

    while(1)
    {
        printf("^^^^^^^^^^ a = %d ^^^^^^^^^^^^\n",a++);
        sleep(1);
    }


    return 0;
}


void *func(void *b)//void *指针要使用必须进行强转
{
    //int num = *((int *)b);//地址取内容之前需要进行强转,b转为int *;

    struct std std1 = *((struct std *)b);

    while(1)
    {
        printf("*** name = %s *** age = %d *** sex = %c ***\n",std1.name, std1.age, std1.sex);
        sleep(1);
    }
}
2、退出线程 — pthread_exit()
    #include <pthread.h>

    void pthread_exit(void *retval);
参数:
    retval:线程退出返回的信息,可自定义,不返回信息填NULL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

void *func(void *b);


int main(int argc, char *argv[])
{

    pthread_t thread1;
    if(0 != pthread_create(&thread1, NULL, func, NULL))
    {
       perror("pthread_create");
       exit(-1);
    }

    while(1)
    {
        printf("^^^^^^^^^^\n");
        sleep(1);
    }

    return 0;
}

void *func(void *b)
{
	int n = 20;

    while(n--)
    {
        if(n == 15)
        {
            pthread_exit(NULL);
        }
        printf("***\n");
        sleep(1);
    }
}
3、线程相关函数接口

1.等待线程 — pthread_join

   #include <pthread.h>

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

参数:
    thread:线程对象
    retval:线程结束信息

返回值:
	成功返回0;失败返回错误号;
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

void *func();
int main(int argc, char *argv[])
{ 
    pthread_t thread1;
    int ret = pthread_create(&thread1, NULL, func, NULL);
    if(ret != 0)
    {   
        perror("pthread_create");
        exit(-1);
    }   
    int n = 2;
    while(n--)
    {   
        printf("----------------\n");
        sleep(1);
    }   
    
    void *result = NULL;

    ret = pthread_join(thread1, &result);
    if(ret != 0)
    {   
        perror("pthread_join");
        exit(-1);
    }   
    printf("%s\n", (char *)result);	//void* 指针使用前需要强转

    return 0;
} 

void *func()
{
    int n = 10; 
    while(n--)
    {   
        printf("aaaaaaaaaa\n");
        sleep(1);
    }   

    pthread_exit("thread1 exit!");

}

/********运行结果********/
----------------
aaaaaaaaaa
----------------
aaaaaaaaaa
...
thread1 exit!
三、线程间通信

一个进程创建的多个线程之间有共享地址空间 — 全局变量 — 可以使用全局变量实现线程间通信;

线程在使用全局数据时,有可能其他线程也在访问该数据,就有可能造成原本数据的破坏;

1、同步

多个线程按约定好的顺序,先后执行;

同步引入信号量来达成;

信号量相当于系统的一种资源,是一个非负整数,值为0表示没有资源,大于0表示有资源;

信号量不能直接访问,必须通过指定的函数接口来完成

1.信号量的初始化 sem_init()

创建一个信号量,给信号量赋初值;

   #include <semaphore.h>

   int sem_init(sem_t *sem, int pshared, unsigned int value);c

   Link with -pthread.
 
参数:
   sem:信号量对象
   pshared:0表示用于线程间通信
   value:给信号量赋初值,不能为负数;0表示一开始就没有资源
返回值:
   成功返回0,失败返回-1
2.p操作 sem_wait()

申请资源,如果没有资源就原地阻塞;

   #include <semaphore.h>

   int sem_wait(sem_t *sem);

参数:
    sem:表示指定的信号量
返回值:
	成功返回0,失败返回-1
if(是否有资源)
{
	执行程序;
	信号量-1;
}
else
{
	程序阻塞;
}
3.v操作 sem_post()

释放资源

   #include <semaphore.h>

   int sem_post(sem_t *sem);

参数:
    sem:表示指定的信号量
返回值:
	成功返回0,失败返回-1
信号量+1
if(是否有阻塞的线程)
{
	唤醒g线程;
}
4.示例

一个进程创建两个线程,一个线程向缓冲区中写入数据,另一个线程读取缓冲区中数据并打印输出;

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <stdlib.h>


void *write_data();
void *read_data();

char buf[64] = {0};

sem_t sem1;

int main(int argc, char *argv[])
{
    pthread_t thread1;
    pthread_t thread2;

    int ret = sem_init(&sem1, 0, 0);
    if(ret < 0)
    {
        perror("sem_init");
        exit(-1);
    }

    if(pthread_create(&thread1, NULL, write_data, NULL) != 0)
    {
        perror("pthread create thread1");
        exit(-1);
    }
    if(pthread_create(&thread2, NULL, read_data, NULL) != 0)
    {
        perror("pthread create thread2");
        exit(-1);
    }

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    return 0;
}
void *write_data()
{
    while(1)
    {
        fgets(buf, 64, stdin);  //数组大小为64,但是末尾要加'\0'所以只能写63个数据
        sem_post(&sem1);   		//释放信号量资源,资源+1,判断是否有阻塞的任务,如果有,将其唤醒    
        //fgets是阻塞函数,不会一直释放资源;sem = 1
    }
}
void *read_data()
{
    while(1)
    {
        sem_wait(&sem1);/*使用资源之后,资源-1*/
        //若先执行读操作,申请信号量资源,初始值为0表示无资源,线程阻塞;时间片结束,执行写操作;如果有资源,程序向下运行,sem-1
        printf("%s", buf);
        sleep(1);
    }
}

只对输出进行了限制,即输入后输出,没有限制输出完成后再输入;

5.生产者和消费者的关系
生产者
sem1 = 1;
while(1)
{
    p(&sem1); //申请自己资源,信号量-1
    write();  //先要执行的操作
    v(&sem2); //释放别人资源,信号量+1
}
消费者
sem2 = 0;
while(1)
{
    p(&sem2);
    read();
    v(&sem1);
}

将示例更改

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <stdlib.h>

void *write_data();
void *read_data();
char buf[64] = {0};

sem_t sem1,sem2;

int main(int argc, char *argv[])
{
    pthread_t thread1;
    pthread_t thread2;

    int ret = sem_init(&sem1, 0, 1);
    if(ret < 0)
    {
        perror("sem_init");
        exit(-1);
    }
    int ret1 = sem_init(&sem2, 0, 0);
    if(ret1 < 0)
    {
        perror("sem_init");
        exit(-1);
    }

    if(pthread_create(&thread1, NULL, write_data, NULL) != 0)
    {
        perror("pthread create thread1");
        exit(-1);
    }
    if(pthread_create(&thread2, NULL, read_data, NULL) != 0)
    {
        perror("pthread create thread2");
        exit(-1);
    }

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    return 0;
}
void *write_data()
{
    while(1)
    {
        sem_post(&sem1);
        fgets(buf, 64, stdin); 
        sem_wait(&sem2);    
    }
}
void *read_data()
{
    while(1)
    {
        sem_post(&sem2);
        printf("%s", buf);
        sem_wait(&sem1);
    }
}
2、互斥

临界数据(公共数据):多个线程都能够访问的数据;

临界区:包含临界资源的一段代码;

互斥锁:来保护临界资源

互斥锁不能直接操作,必须通过接口函数完成访问;

每个要保护数据的线程都要加锁!

1.互斥锁的初始化 — pthread_mutex_init()
   #include <pthread.h>
   //定义一把锁
   pthread_mutex_t mutex;
   int pthread_mutex_init(pthread_mutex_t *mutex,
       			const pthread_mutexattr_t *attr);

参数:
    mutex:互斥锁对象
    attr:互斥锁属性//NULL表示缺省属性
返回值:
    成功返回0;失败返回-1
2.申请锁 — pthread_mutex_lock()
3.释放锁 — pthread_mutex_unlock()
   #include <pthread.h>

   int pthread_mutex_lock(pthread_mutex_t *mutex);
   int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数:
    mutex:互斥锁对象
返回值:
    成功返回0;失败返回-1

利用互斥锁,第一个线程访问临界数据时,第二个线程不能访问;

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <stdlib.h>

void *func();

int num1 = 0;
int num2 = 0;
int count = 0; 
//pthread_mutex_t mutex1;

int main(int argc, char *argv[])
{
    pthread_t thread1;
    
    //int ret = pthread_mutex_init(&mutex1, NULL);
    //if(ret < 0)
    //{
    //   perror("pthread_mutex_init");
    //    exit(-1);
    //}
    
    if(pthread_create(&thread1, NULL, func, NULL) != 0)
    {
        perror("pthread");
        exit(-1);
    }

    while(1)
    {
        //pthread_mutex_lock(&mutex1);
        num1 = count;
        num2 = count;
        count++;
        //pthread_mutex_unlock(&mutex1);
    }
    return 0;
}

void *func()
{
    while(1)
    {
        //pthread_mutex_lock(&mutex1);
        if(num1 != num2)
        {
            printf("num1 = %d, num2 = %d\n",num1, num2);
        }
        //pthread_mutex_unlock(&mutex1);
    }
}

/*****************运行结果*****************/
num1 = 209169771, num2 = 209169770
解开注释后,利用互斥锁,不会打印数据。
四、进程间通信

每个进程的用户空间相互独立,内核空间互通;所以进程间的通信是借助内核空间完成通信;

UNIX:管道、信号 Linux:管道、信号、system V IPC对象(系统5 IPC对象);

system V IPC对象:共享内存、消息队列、信号灯集;

以上用于本地通信;只有套接字用于网络通信,所以进程间通信方式一共有6种;

1、管道

由用户空间调用函数接口,在内核空间创建管道;所有访问要经过内核空间;

1.1无名管道_pipe

创建管道之后在文件系统中==不可见;==有固定的读端(pipefd[0])和写端(pipefd[1]);

半双工的通信机制;(只能读或者写,不能同时读写)

无名管道只能用于具有亲缘关系的进程间通信;

   #include <unistd.h>

   int pipe(int pipefd[2]);

参数:
    用于存放读端和写端的文件描述符的数组;
    读端(pipefd[0])和写端(pipefd[1])

返回值:
	成功返回0;失败返回-1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{ 
    int pfd[2];
    int ret = pipe(pfd);  //传递数组时传递数组的地址即可
    if(ret < 0)
    {
        perror("pipe");
        exit(-1);
    }

    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        exit(-1);
    }

    if(pid == 0)
    {
        char buf[64] = {0};
        fgets(buf, 64, stdin);
        write(pfd[1], buf, 64);
        exit(0);
    }
    else
    {
        sleep(2);
        char buf[64] = {0};
        read(pfd[0], buf, 64);	//阻塞函数,直到有数据发过来;
        printf("buf:%s",buf);   //fgets会自动获取stdin的换行符
        wait(NULL);         	//等待回收子进程
    }

    return 0;
} 

/*********运行结果*************/
nihao
buf:nihao
无名管道的读特性和写特性

读特性:

写端存在:
	缓冲区有数据---返回从缓冲区中读到的字节数;
	缓冲区无数据---阻塞
写端不存在:(close pfd[1])
	缓冲区有数据---返回从缓冲区中读到的字节数;
	缓冲区无数据---直接返回0

写特性:

读端存在:
	缓冲区有空间---返回写入的字节数;
	缓冲区无空间---阻塞,直到有空间才会继续写入;(计算无名管道的空间大小)
读端不存在:(close pfd[0])
	无论有无空间---管道破裂(13号信号),程序强行结束;

计算无名管道的空间大小

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

int main()
{
    int pfd[2];
    pipe(pfd);
    
    char buf[1] = {0};
    int ret = 0;
    int count = 0;
    while(1)
    {
        ret = write(pfd[1], buf, 1);
        if(ret < 0)
        {          
            perror("write");
            exit(-1);
        }
        else
        {
            count++;
            printf("count = %d\n",count);
        }
    }
}
/*************运行结果***************/
count = 65534
count = 65535
count = 65536
无名管道大小为65536/1024=64k

捕获管道破裂信号

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{ 
    int pfd[2] = {0};
    pipe(pfd);
    close(pfd[0]);

    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        exit(-1);
    }

    if(pid == 0)
    {
        write(pfd[1], "hello", 5);
        exit(0);        //如果管道没有破裂正常退出
    }
    else
    {

        int status;

        wait(&status);
        printf("true = %d ret = %d signal = %d signal_val = %d\n",WIFEXITED(status),WEXITSTATUS(status),WIFSIGNALED(status),WTERMSIG(status)
);		//0---非正常退出;0---exit非正常退出;1---进程被信号打断;13---信号的编号
    }
    return 0;
} 
/*************运行结果*************/
true = 0 ret = 0 signal = 1 signal_val = 13
1.2有名管道_fifo

创建有名管道之后,以文件的形式存在;(mkfifo创建的管道在文件中可见)

有名管道没有固定的读端和写端;可以通过文件描述符指定对管道进行读写;

   #include <sys/types.h>
   #include <sys/stat.h>
   int mkfifo(const char *pathname, mode_t mode);

参数:
	pathname: 创建有名管道的名字(包含路径)
        //不加路径表示在当前路径下创建

返回值:
	成功返回0,失败返回-1;

一个进程写入,一个进程读取并打印;

fopen有缓冲区,一般用来打开普通文件,open无缓冲区,打开其他文件;

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stlib.h>

int main()
{
    int ret = mkfifo("myfifo", 0664);
    if(ret < 0)
    {
        perror("mkfifo");
        exit(-1);
    }
   
    return 0;
}   

发送端

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stlib.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        printf("Usage: %s <fifoname>", argv[0]);
        exit(-1);
    }
    
    int fd = open(argv[1], O_WRONLY);	//打开方式为读写
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }
    
    printf("open fifo!\n");
    
    char buf[64] = {0};
    {
        while(1)
        {
            fgets(buf, 64, stdin);
            write(fd, buf, 64);
        }
    } 
    return 0;
}

读取端

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stlib.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        printf("Usage: %s <fifoname>", argv[0]);
        exit(-1);
    }
    
    int fd = open(argv[1], O_RDONLY);	//打开方式为只读
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }
    
    printf("open fifo!\n");
    
    char buf[64] = {0};
    {
        while(1)
        {
            read(fd, buf, 64);
            printf("%s", buf);
        }
    } 
    return 0;
}

读端和写端同时存在,管道才可以打开;

2、 信号

信号是在软件层次上的一种异步中断;(中断一般由硬件设备产生)

处理信号的方式:默认处理、忽略、捕获(修改为非默认处理方式);

查看信号相关指令
kill -l		//显示当前系统可用信号	宏 == 数字
	//1-31号信号继承于unix;为常用信号;
操作信号相关指令

kill:

kill -9 1234	//向1234进程发送信号9
kill -9 -1234	//向1234进程组发送信号9		进程组:有亲缘关系的所有进程
kill -9 -1		//向除了init进程外的其他所有进程发送信号9

killall:

killall a.out	//向进程名为a.out的所有进程发送默认信号15(终止)
信号相关接口函数

kill:向指定进程发送指定信号;

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

int kill(pid_t pid, int sig);

参数:
	pid:进程号;
	sig:指定信号;

返回值:
	成功返回0,失败返回-1

raise:向当前进程发送信号;

#include <signal.h>

int raise(int sig);
alarm定时器

以秒为单位将SIGALRM信号发送给调用进程。

定时器在一个进程中只能存在一个;

在定时器结束后会向当前进程发送信号14(SIGALRM),结束当前进程;

	#include <unistd.h>

	int alarm(int seconds);

参数:
    定时秒数
    
返回值:
    如果一个定时器后没有其他定时,返回0;
    返回>0,表示上一次定时的剩余时间;
pause

在当前函数位置挂起,等待接收SIGALRM信号;

	#include <unistd.h>

	int pause(void);
signal
   #include <signal.h>

   typedef void (*sighandler_t)(int);

   sighandler_t signal(int signum, sighandler_t handler);

参数:
    signum:想要捕获的信号;
    handler:信号处理函数,也可以填SIG_IGN或SIG_DFL
    sIG_ IGN:表示捕获指定信号,选择处理方式为忽略
	sIG_ DFL:表示捕获指定信号,选择处理方式为默认
3. system V IPC对象:共享内存、消息队列、信号灯集;

都是在内核空间中创建;

打开或创建IPC通道:shm_get、msg_get、sem_get

不同的进程访问同一个IPC对象时,通过key来创建的内核空间,才能被不同的进程通过key进行访问。

IPC对象在创建之后,不会自动删除,只能由用户手动删除;

IPCS — 查看系统IPC对象;

3.1 共享内存

是进程间通信机制中通信效率最高的通信方式;

通过内存映射技术,取消了频繁的系统调用(拷贝);

共享内存实现流程:
(1)创建或者打开共享内存	 shmget()
(2)将共享内存映射到用户空间 shmat()
(3)操作共享内存
(4)取消内存映射		shmdt()
(5)删除共享内存		shmctl() 
3.2 消息队列

通过消息结构体中的type可以在传输消息时选择性地读取;

消息队列使用流程:
创建或者打开消息队列   msgget()
发送消息             msgsnd()
接收消息             msgrcy()
删除消息队列          msgctl()
3.3 信号灯集

信号灯就是信号量的集合,申请多个资源时,避免死锁。

信号灯集使用流程:
1、创建或者打开信号灯集    semget()
2、初始化信号灯集         semctl()
3、PV操作               semop()
4、删除信号灯集           semctl()

pes.h>
#include <signal.h>

int kill(pid_t pid, int sig);

参数:
pid:进程号;
sig:指定信号;

返回值:
成功返回0,失败返回-1;


raise:向当前进程发送信号;

```c
#include <signal.h>

int raise(int sig);
alarm定时器

以秒为单位将SIGALRM信号发送给调用进程。

定时器在一个进程中只能存在一个;

在定时器结束后会向当前进程发送信号14(SIGALRM),结束当前进程;

	#include <unistd.h>

	int alarm(int seconds);

参数:
    定时秒数
    
返回值:
    如果一个定时器后没有其他定时,返回0;
    返回>0,表示上一次定时的剩余时间;
pause

在当前函数位置挂起,等待接收SIGALRM信号;

	#include <unistd.h>

	int pause(void);
signal
   #include <signal.h>

   typedef void (*sighandler_t)(int);

   sighandler_t signal(int signum, sighandler_t handler);

参数:
    signum:想要捕获的信号;
    handler:信号处理函数,也可以填SIG_IGN或SIG_DFL
    sIG_ IGN:表示捕获指定信号,选择处理方式为忽略
	sIG_ DFL:表示捕获指定信号,选择处理方式为默认
3. system V IPC对象:共享内存、消息队列、信号灯集;

都是在内核空间中创建;

打开或创建IPC通道:shm_get、msg_get、sem_get

不同的进程访问同一个IPC对象时,通过key来创建的内核空间,才能被不同的进程通过key进行访问。

IPC对象在创建之后,不会自动删除,只能由用户手动删除;

IPCS — 查看系统IPC对象;

3.1 共享内存

是进程间通信机制中通信效率最高的通信方式;

通过内存映射技术,取消了频繁的系统调用(拷贝);

共享内存实现流程:
(1)创建或者打开共享内存	 shmget()
(2)将共享内存映射到用户空间 shmat()
(3)操作共享内存
(4)取消内存映射		shmdt()
(5)删除共享内存		shmctl() 
3.2 消息队列

通过消息结构体中的type可以在传输消息时选择性地读取;

消息队列使用流程:
创建或者打开消息队列   msgget()
发送消息             msgsnd()
接收消息             msgrcy()
删除消息队列          msgctl()
3.3 信号灯集

信号灯就是信号量的集合,申请多个资源时,避免死锁。

信号灯集使用流程:
1、创建或者打开信号灯集    semget()
2、初始化信号灯集         semctl()
3、PV操作               semop()
4、删除信号灯集           semctl()
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值