进程知识总结

进程

查看进程

Windows下查看进程

Linux下查看进程

  1. ps aux
  2. ps -ef
  3. top
  4. pstree
  5. ps axj

什么是进程

进程的概念

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

进程和程序的区别

程序(a.out)是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念
进程(./a.out)是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡

进程是程序执行和资源(内存)管理的最小单位

为什么说进程是程序执行和资源管理的最小单位?
因为每一个进程都有一个0~4G的虚拟内存

为什么要有虚拟内存?--->多任务、安全

如何区分不同的进程-->PID(进程号) 

进程是由进程创建的,有父进程和子进程

进程的类型

交互进程

该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。

在前台运行:

后台运行:

批处理进程

该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。

守护进程

该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。

进程运行状态

(1)运行态:此时进程或者正在运行,或者准备运行。

(2)等待态:此时进程在等待一个事件的发生或某种系统资源。

可中断

不可中断

(3)停止态:此时进程被中止。

(4)僵死态:这是一个已终止的进程,但还在进程向量数组中占有一个task_struct结构体。

为了更好的管理Linux所访问的资源,系统在内核头文件include/linux/sched.h定义了进程控制块(PCB)结构体task_struct来管理每个进程的资源内核空间进程资源即PCB相关的信息,包括进程控制块本身,打开的文件表项、当前目录、当前终端信息、线程基本信息、可以访问内存地址空间、PID、PPID、UID、EUID等,也就是说,内核通过PCB结构体可以访问到进程的所有资源信息

(5)死亡态:不会显示出来。

进程的状态切换图

进程的模式 

 

进程相关的的系统调用

fork()
  

 fork函数用于创建一个子进程,该子进程几乎拷贝了父进程的全部内容

注意:fork之后,父进程和子进程存在资源竞争的关系,谁先调度由调度算法来决定
           子进程执行是在fork函数的返回处 

getpid()、getppid()

getpid返回当前进程标识,getppid返回父进程标识。

 
 进程退出

exit()函数是一个高级的库函数,它执行一些清理操作并返回一个状态码给操作系统。它使用_exit来结束程序。
_exit是一个底层的系统调用,它会立即结束程序,不执行任何清理操作。
主函数的return语句不会自动调用exit或_exit。它只是直接从主函数返回,这会导致程序结束。

 

 

孤儿进程

父进程先于子进程退出,子进程会被systemd进程收养,子进程会变成后台进程 

僵尸进程

子进程先退出,父进程没有及时回收子进程的资源(PCB结构体),此时就成为了僵尸进程
如果父进程不退出,子进程会一直保持僵死状态,直到父进程退出,被systemd回收

wait()、waitpid()

可以调用wait或者waitpid避免僵尸进程,实现子进程退出时父进程及时回收子进程的资源

wait函数:
调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到了一个信号为止。如果该进
程没有子进程或者其子进程已经结束,wait函数会立即返回。

waitpid函数:
功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)。

 

exec函数族

 exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。

 

l:list 以列表的形式传参
v:vector 数组
p:path 系统会自动从环境变量“$PATH”所包含的路径中进行查找 

 

守护进程

概念

从开始运行直到关闭整个系统才退出;如果想让某个进程不会因为用户或终端的变化而受到影响,就必须把这个进程变成一个守护进程。

特点

1.从系统启动开始运行,系统关闭时停止运行
2.后台进程,与终端无关 

守护进程与终端之间的关系

1.不能和终端具有亲缘关系
2.与终端无关,也就是要把控制终端变成? 

守护进程的创建步骤

1.创建子进程,父进程退出子进程变成孤儿进程,然后由1号init进程收养(使用fork函数)

2.子进程创建新会话。调用setsid创建新的会话,摆脱原会话,原进程组,原终端的控制,自己成为新会话的组长(使用setsid函数)

3.将当前目录改为根目录。正在运行的进程文件系统不能卸载,如果目录要回退,则此时进程不能做到,为了避免这种麻烦,以根目录为当前目录(使用chdir函数)

4.重设文件权限掩码。子进程的文件权限掩码是复制的父进程的,不重新设置的话,会给子进程使用文件带来诸多麻烦(使用umask函数)

5.关闭不需要的文件描述符。子进程的文件描述符也是从父进程复制来的,那些不需要的文件描述符永远不会被守护进程使用,会白白的浪费系统资源,还可能导致文件系统无法结束(使用close函数)

线程

线程的相关概念

1.线程是系统调度的最小单位
2.为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念也被称为线程
3.Linux里同样用task_struct来描述一个线程。线程和进程都参与统一的调度。

一个进程中的多个线程共享以下资源:
可执行的指令、静态数据、进程中打开的文件描述符、信号处理函数、当前工作目录、用户ID、用户组ID

创建线程

注意:线程之间也存在资源竞争的关系

线程的等待函数

 ​​​​​​

将子线程设置为游离态

 

线程退出函数

 

多线程

相关概念

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

优点:线程间很容易进行通信
通过全局变量实现数据共享和交换

缺点:多个线程同时访问共享对象时需要引入同步和互斥机制

 同步

相关概念

同步指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
由信号量来决定线程是继续运行还是阻塞等待

初始化

P/V操作

sem_wait( ):P操作 -1

sem_wait在信号量大于0时,它将这个信号量的值减1,若信号量的值为0,sem_wait将会阻塞

sem_post( ):V操作 +1

sem_post相当与V操作,它将信号量的值加1,同时唤醒等待的进程

注意:也可以保护临界资源的完整性,而且实现同步

示例:

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

//创建信号量
sem_t sem1,sem2,sem3;
void *threadFunc1(void *argv)
{
	while(1)
	{
		//执行P操作
		sem_wait(&sem1);
		printf("视屏采集......\n");
		sleep(1);
		sem_post(&sem2);
	}
}
void *threadFunc2(void *argv)
{
	while(1)
	{
		sem_wait(&sem2);
		printf("人脸监测......\n");
		sleep(1);
		sem_post(&sem3);
	}
}
void *threadFunc3(void *argv)
{
	while(1)
	{
		sem_wait(&sem3);
		printf("人脸识别......\n");
		sleep(1);
		sem_post(&sem1);
	}
}

int main(void)
{
	//初始化信号量
	sem_init(&sem1,0,1);
	sem_init(&sem2,0,0);
	sem_init(&sem3,0,0);
	//1.创建线程的id号
	pthread_t thId1,thId2,thId3;
	//2.创建线程
	pthread_create(&thId1,NULL,threadFunc1,NULL);
	pthread_create(&thId2,NULL,threadFunc2,NULL);
	pthread_create(&thId3,NULL,threadFunc3,NULL);
	//3.等待
	pthread_join(thId1,NULL);
	pthread_join(thId2,NULL);
	pthread_join(thId3,NULL);
	while(1);
	return 0;
}

 互斥

相关概念

1.引入互斥锁的目的是用来保证共享数据操作(临界资源)的完整性。
2.互斥锁主要用来保护临界资源
3.每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源
4.线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止

步骤

1.创建锁

pthread_mutex_t myMutex;

2.初始化锁

3.上锁

4.解锁

5.销毁锁

pthread_mutex_destroy(&myMutex);

通信的相关概念

单工:A-->B (键盘输入)

半双工:A--->B 或者 B--->A (对讲机)

双工:A--->B 同时 B-->A(打电话)

同步:(走路)

异步:(外卖)

 

通信协议

双方约定好的规则

通信方式 

传统的进程间通信方式

无名管道(pipe)、有名管道(fifo)和信号(signal)

System V IPC对象

   共享内存(share memory)、消息队列(message queue)和信号灯(semaphore)

BSD

套接字(socket)

无名管道

相关概念 

特点:
1.只能用于具有亲缘关系的进程之间的通信(父子进程,兄弟进程)
2.半双工通信,具有固定的读端fd[0]和写端fd[1]
3.管道可以看成是一-种特殊的文件,对于它的读写可以使用文件I0如read、write函数。

通信方式:
管道是基文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

创建无名管道

 

为什么无名管道只能用于具有亲缘关系间的进程的通信?
无名管道没有名字,不同的进程无法获取同一个文件描述符,具有亲缘关系的进程之间可以继承,从而得到同一个文件描述符。

注意:

(1)当管道中无数据时,读操作会阻塞

(2)向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将会一直阻塞。

(3)只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Broken pipe错误)-->管道破裂

 有名管道

相关概念

1.有名管道可以使互不相关的两个进程进行相互通信。
2.有名管道可以通过路径名来指出,并且在文件系统中可见。
3.进程通过文件IO操作有名管道。
4.有名管道遵循先进先出原则。
5.不支持如lseek()操作。

创建有名管道

 

注意:只有读端或者写端存在的时候,系统会阻塞,直到有另一个进程(包括自己)以另一种方式打开管道

信号

相关概念

1.信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。
2.信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3.如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

 

用户进程对信号的响应方式

忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
执行缺省操作: Linux对每种信号都规定了默认操作。 

列出所有的信号

 

其中SIGUSR1,SIGUSR2常作为自定义信号 

信号安装 or 信号注册函数

 

案例1:忽略信号

案例2:采用默认操作

案例3:自定义信号处理函数

alarm 

alarm()也称为闹钟函数,它可以在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALARM信号

pause

pause()函数是用于将调用进程挂起直到收到信号为止 

kill 

发送信号给进程或进程组

raise

raise函数允许进程向自己发送信号

共享内存 

相关概念

SystemV提供的IPC机制主要由消息队列、共享内存、信号量三种机制。和文件一样,IPC在使用前必须先创建,每种IPC都有特定的生产者、所有者和访问权限。使用ipcs命令查看当前系统正在使用的IPC工具。

共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。
为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。
进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高效率。
由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等。

 共享内存的创建步骤

1.创建/打开共享内存

2.映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问

3.撤销共享内存映射

 4.删除共享内存对象

有名信号量

有名信号量:进程间的同步

无名信号量:线程间的同步

 创建一个有名信号量

sem_open

sem_wait( ):P操作 -1

sem_wait在信号量大于0时,它将这个信号量的值减1,若信号量的值为0,sem_wait将会阻塞

sem_post( ):V操作 +1

sem_post相当与V操作,它将信号量的值加1,同时唤醒等待的进程

sem_close

 流程图

消息队列

相关概念

1.消息队列是IPC对象的一种
2.消息队列由消息队列ID来唯一标识
3.消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
4.消息队列可以按照类型来发送/接收消息
注意:消息类型不能为0 

消息队列的创建步骤 

1.创建并打开消息队列

2.添加消息

 3.接收消息

4.删除消息队列

 

实例:

多进程实现

父进程发送:                       父进程接收:

type:100                           type:100

子进程接收:                       子进程发送:

type:200                           type:200

read.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <string.h>

#define N 30
typedef struct msgbuf
{
	long mType;//消息类型
	char mText[N];//消息内容
}Msg;

int main(void)
{
	//获取key值
	key_t key = ftok(".",6);
	if(key < 0)
	{
		perror("fork error");
		return -1;
	}
	//获取ID值
	int msgID = msgget(key,IPC_CREAT | 0666);
	if(msgID < 0)
	{
		perror("msgget error");
		return -1;
	}
	//添加消息
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid > 0)
	{
		//父进程
		//定义一个结构体变量
		Msg msg = {0};
		while(1)
		{
			int res = msgrcv(msgID,(void *)&msg,sizeof(msg.mText),100,0);
			if(res < 0)
			{
				perror("msgrcv error");
				msgctl(msgID,IPC_RMID,NULL);
				return -1;
			}
			printf("type:%ld text:%s\n",msg.mType,msg.mText);
			sleep(1);
		}
		//删除消息队列
		msgctl(msgID,IPC_RMID,NULL);
	}
	else
	{
		//子进程发送
		Msg msg2 = {0};
		msg2.mType = 200;
		strcpy(msg2.mText,"Hello from child");
		while(1)
		{
			int ret = msgsnd(msgID,(void *)&msg2,sizeof(msg2.mText),0);
			if(ret < 0)
			{
				perror("msgsnd error");
				msgctl(msgID,IPC_RMID,NULL);
				return -1;
			}
			sleep(1);
		}
	}
	return 0;
}
write.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <string.h>

#define N 30
typedef struct msgbuf
{
	long mType;//消息类型
	char mText[N];//消息内容
}Msg;

int main(void)
{
	//获取key值
	key_t key = ftok(".",6);
	if(key < 0)
	{
		perror("fork error");
		return -1;
	}
	//获取ID值
	int msgID = msgget(key,IPC_CREAT | 0666);
	if(msgID < 0)
	{
		perror("msgget error");
		return -1;
	}
	//添加消息
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid > 0)
	{
		//父进程发送
		//定义一个结构体变量
		Msg msg = {0};
		msg.mType = 100;
		strcpy(msg.mText,"Hello from parent");
		//发送消息
		while(1)
		{
			int ret = msgsnd(msgID,(void *)&msg,sizeof(msg.mText),0);
			if(ret < 0)
			{
				perror("msgsnd error");
				msgctl(msgID,IPC_RMID,NULL);
				return -1;
			}
			sleep(1);
		}
	}
	else
	{
		//子进程
		Msg msg2 = {0};
		while(1)
		{
			int res = msgrcv(msgID,(void *)&msg2,sizeof(msg2.mText),200,0);
			if(res < 0)
			{
				perror("msgrcv error");
				msgctl(msgID,IPC_RMID,NULL);
				return -1;
			}		
			printf("type:%ld text:%s\n",msg2.mType,msg2.mText);
			sleep(1);
		}
		//删除消息队列
		msgctl(msgID,IPC_RMID,NULL);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值