进程、线程

一、什么是进程

(一)linux下查看进程

ps aux  ,  ps  axj   ,pa -ef   ,   pstree  ,  top  

(二)进程的概念

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

进程是程序执行和资源(内存)管理的最小单位   (因为每个进程都有一个0~4G的虚拟内存)(虚拟内存的存在是为了实现多任务,安全)

(三)进程和程序的区别

进程(./a.out)是一个动态的概念,它是程序执行的过程,包括创建调度、消亡

程序(a.out)是静态的,是保存在磁盘是的一些指令的有序集合,没有任何要执行的概念

(四)如何区分不同的进程

依靠PID(进程号)区分

(五)进程分类

1、交互进程

由shell控制和运行,既可以在前台运行,又可以在后台运行

2、批处理进程

不属于任何终端,它被提交到一个队列中以便顺序执行

3、守护进程

(1)特点

在后台运行,与终端无关。

系统启动时开始运行,系统关闭时停止运行

(2)进程和终端的关系

进程的控制终端相同,具有亲缘关系

(3)守护进程的创建步骤

a.创建子进程,父进程退出  

b.在子进程中创建新的会话

c.改变当前路径为根目录

d.重设文件掩码

e.关闭文件描述符

#include <stdio.h>
#include <unistd.h>
#include<sys/types.h>
#include <stdlib.h>
#include <time.h>
#include<fcntl.h>
#include <sys/stat.h>
#include<string.h>
int main(int argc, const char *argv[])
{
	//1.创建子进程,让父进程先退出
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid > 0)
	{
		//父进程退出
		exit(0);
	}
	//2.在子进程中创建新会话
	int ret = setsid();
	//3。改变当前目录为根目录
	chdir("/");
	//4。修改文件掩码
	umask(0);
	//5。关闭文件描述符
	int i=0;
	for(int i = 0; i < getdtablesize();i++)
	{
	close(i);
	}
	
	
	//写日志
	int fw = open("daemon.log",O_RDWR | O_CREAT,0777);
	if(fw < 0)
	{
		perror("open error");
		return -1;
	}
	time_t tm;
	char buf[20];
	while(1)
	{
		memset(&tm,0,sizeof(time_t));
		//获取从1970-1-1到现在的秒数
		time(&tm);
		char *ptr = ctime(&tm);
		strcpy(buf,ptr);
		ret = write(fw,buf,sizeof(buf));
		//ret = write(fw,"hello",5);
		if(ret > 0)
		{
			printf("wrtie ok!\n");
		}
		sleep(1);
	}
	return 0;
}

(六)进程运行的状态

1、运行状态

(1)运行态

(2)等待态

(3)停止态

(4)僵尸态

(5)死亡太

2、

(七)进程的模式

1、用户模式

2、内核模式

3、相关调用

nice:按用户指定好的优先级运行程序

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

(八)进程相关的系统调用

1、创建进程

pit_t   fork();

2、获取自己的PID

pid_t  gatpid();

3、获取父进程的PID

pid_t  getppid();

4、退出进程

_exit()  (不刷新缓冲区)

exit()       (刷新缓冲区)

return(  刷新缓冲区)

#include <stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
	printf("1111\n");
	pid_t pid = fork();
	//父进程
	if(pid >0)
	{
		/*
		while(1)
		{
			//打印自己的pid
			printf("i am parent,pid=%d\n",getpid());
			sleep(1);
		}
		*/
		int stat;
	//	wait(NULL);//阻塞等待函数  避免僵尸进程
	while(1)
	{
		int ret = waitpid(-1,&stat,WNOHANG);
		printf("ret = %d\n",ret);
		if(ret >0)
		{
			if(WIFEXITED(stat))
			{
				printf("子进程正常退出%d\n",WEXITSTATUS(stat));
			}
			else if(WIFSIGNALED(stat))
			{
				printf("异常退出%d\n",WTERMSIG(stat));
			}
		}
		sleep(1);
		printf("i am parent\n");

	}
		
	}
	//子进程
	if(0 == pid)
	{
		
			/*
			if(i >2)
			{
				printf("exit--------");
				//退出进程   exit  ,_exit  ,return 
				//exit(0);//刷新缓冲区
			//	_exit(0);//不刷新缓冲区,不执行上面的printf
			}
			*/
			//打印父进程,自己进程pid
	//		sleep(1);
			printf("i am child,ppid=%d pid=%d\n",getppid(),getpid());
		
	}
	return 0;
}

(九)孤儿进程

1、概念

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

(十)僵尸进程

1、概念

子进程先退出,父进程没有及时回收子进程的资源(PCB结构体)(没有及时查看子进程的退出状态

2、避免僵尸进程

子进程退出时及时回收子进程的资源,调用wait    ,     waitpid

(十一)exec函数族

提供了一种在进程中启动另一个程序的方法

二、进程间通信的方式

(一)传统的unix通信方式

1、无名管道

(1)相关概念

     只能用于具有亲缘关系的进程之间的通信

    半双工的通信方式,有固定的读端fd[0]和写端fd[1]

    管道可以看成是特殊的文件(内核中的一块内存),可以用文件IO对其读写

    管道通信基于文件描述符

(2)创建

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/wait.h>
int main()
{
	//定义存放文件描述符的数组
	int fd[2] = {-1,-1};
	//创建无名管道
	int ret = pipe(fd);
	if(ret < 0)
	{
		perror("pipe error");
		return -1;
	}
	//关闭读端  ,测试管道破裂
	close(fd[0]);
	//创建进程
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		close(fd[0]);
		close(fd[1]);
		return -1;
	}
	else if(pid > 0)
	{
		/*
		//父进程    往管道里写
		//关闭无名管道读端
		close(fd[0]);
		char buf[30]={'\0'};
		//从标准输入读到buf
		fgets(buf,sizeof(buf),stdin);
		printf("w:");
		//写进管道
		write(fd[1],buf,sizeof(buf));
		sleep(1);
		*/
		//父进程  
		//	close(fd[0]);
		int stat;
		int ret = wait(&stat);
		if(ret < 0)
		{
			perror("waitpid error");
			close(fd[1]);
			return -1;
		}
		else
		{
			if(WIFSIGNALED(stat))
			{
				printf("子进程同过信号退出%d\n",WTERMSIG(stat));
			}

			if(WIFEXITED(stat))
			{
				printf("正常退出%d\n",WEXITSTATUS(stat));
			}
		}

	}

	else
	{
		//子进程  写

		//关闭无名管道读端
		//	close(fd[0]);
		printf("w:");
		char buf[30]={'\0'};
		//从标准输入读到buf
		fgets(buf,sizeof(buf),stdin);
	
		//写进管道
		write(fd[1],buf,sizeof(buf));
		close(fd[1]);
		/*
		//子进程    从管道里读出来
		//关闭无名管道写端
		close(fd[1]);
		char buf[30] = {'\0'};
		printf("r:");
		//从无名管道读到buf
		read(fd[0],buf,sizeof(buf));
		printf("%s\n",buf);
		*/
	}
	/*
	//关闭文件描述符
	close(fd[0]);
	close(fd[1]);
	*/
	return 0;
}

(3)注意

当管道中无数据,读操作会阻塞

写数据时,管道缓冲区有空闲,写进程就一种试图写入,如果不读走缓冲区中的数据,写操作会阻塞

只有在读端存在时,向管道写数据才有意义,否则会管道破裂

2、有名管道

(1)相关概念

可以使互不相关的两个进程相互通信,通过名字来指明路径,并在文件系统中可见

进程通过文件IO来操作有名管道

有名管道遵循先进先出规则

不支持lseek()存在

(2)创建

//创建有名管道(管道本质是个文件)
    int ret = mkfifo("f1.fifo",0777);
	
	if(ret < 0 )
	{
		perror("f1.fifo error");
		return -1;
	}

(3)注意

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

3、信号

(1)相关概念

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

信号可以直接进行用户空间和内核空间之间的交互,内核进程可以利用它来通知用户进程发生了哪些系统事件

如果进程当前并未执行状态,则该信号就有内核保存起来,只到进程执行在传递

(2)信号响应

忽略,捕捉,执行缺省操作

(3)列出所有信号

kill   -l

  (4)安装信号

(5)信号相关的系统调用

时钟函数:alarm

挂起过程:pause

发送信号给进程或进程组:kill

进程向自己发送信号:raise

(二)systemV提供的通信方式

1、共享内存

(1)相关概念

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

共享内存是一种最为高效的通信方式,进程可以直接读写内存,不协议拷贝。

内核专门留了一个内存块,可以由进程将其映射到自己的私有空间。

2、共享内存的创建

(1)创建并代开共享内存  shmget

  (2)映射共享内存   shmat

  (3)撤销共享内存  shmdt

  (4)删除共享内存  shmctl

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
void sigFunc(int signum)
{
	if(signum == SIGUSR1)
	{
		printf("");
	}
}
int main()
{
	//1。创建并打开共享内存
	//获取key值
	key_t key = ftok(".",23);
	if(key < 0)
	{
		perror("ftok error");
		return -1;
	}
	printf("key:%d\n",key);
	//获取ID值
	int shmId = shmget(key,100,IPC_CREAT | 0777);
	if(shmId < 0)
	{
		perror("shmget error");
		return -1;
	}
	printf("shmId : %d\n",shmId);
	//2。影射
	void *pAddr = shmat(shmId,NULL,0);
	if((void *)-1 == pAddr)
	{
		perror("shmat error");
		//返回之前删除共享内存
		shmctl(shmId,IPC_RMID,NULL);
		return -1;
	}
	//获取该进程的pid,写入共享内存
	*(int *)pAddr = getpid();

	//操作
	//安装信号
	signal(SIGUSR1,sigFunc);
	while(1)
	{
		//挂起该进程直到W写完发信号
		pause();
		//W入quit退出
		if(0 == strcmp((char *)pAddr,"quit"))
		{
			break;
		}
		printf("r:%s\n",(char *)pAddr);

	}	
	//取消映射
	int ret = shmdt(pAddr);
	if(ret < 0)
	{
		perror("shmdt error");
		shmctl(shmId,IPC_RMID,NULL);
		return -1;
	}
	return 0;

}

2、消息队列

/*通过消息队列发消息,msgd_r收消息*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define N 30
typedef struct msgbuf
{
	long mtype;
	char mtext[N];
}Msg;
int main()
{
	//获取key值
	key_t key = ftok(".",8);
	if(key < 0)
	{
		perror("ftok error");
		return -1;
	}
	//获取ID值
	int msgId = msgget(key,IPC_CREAT |0666);
	if(msgId < 0)
	{
		perror("msgget error");
		return -1;
	}
	//添加消息
	//定义一个结构体变量
	Msg msg = {0};
	int i = 0;
	for(i = 0; i < 3 ;i++)
	{
		printf("type:");
		scanf("%ld",&msg.mtype);
		printf("text:");
		scanf("%s",msg.mtext);
		msgsnd(msgId,(void *)&msg,sizeof(msg.mtext),0);
	}

	//删除消息队列
//	msgctl(msgId,IPC_RMID,NULL);
	return 0;
}

3、信号量

(三)通信的相关概念

1、通信的模式

单工、半双工、双工、同步、异步

2、通信协议

双方约定好的规定

三、什么是线程

(一)线程的相关概念

1、线程是系统调度的最小单位

由于进程的地址空间是私有的,因此进程上下文切换时,系统开销比较大

为了提高系统的性能,许多操作系统规范里引用了轻量级的进程的概念,也被称为线程

在同一个进程中创建的线程共享该进程的地址空间

linux里同样用task_struct来描述一个线程。线程和进程都参与统一的调度

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

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

(二)线程的创建

1、头文件

#include <pthread.h>

2、函数原型

int  pthread_create(pthread_t  * thread,const pthread_atty_t  *attr, void *(*routine)(void *),void * arg)

3、参数

thread:创建的线程

attr:指定线程的属性,NULL表示使用缺省属性

routine:线程执行函数    返回值为void *类型,参数为void * 类型的函数

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

4、返回值

成功:0

失败:-1

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

(三)线程等待函数

1、头文件

#include <pthread.h>

2、函数原型

int pthread_join(pthread_t thread,void **value_ptr)

3、函数参数

thread :要等待的线程

value_ptr:指针*value_ptr指向线程返回的参数

4、返回值

成功:0

失败:-1

(四)将线程设置成为游离状态

1、头文件

#include <pthread.h>

2、函数原型

int  pthraed_detach(pthread_t thread)

3、函数参数

pthread :要设置的线程

4、返回值

成功:0

失败:-1

(五)线程退出函数

1、头文件

#include<pthread.h>

2、函数原型

int  pthread_exit(void *value_ptr)

3、函数参数

value_ptr:线程退出时返回的值

4、返回值

成功:0

失败:-1

(六)多线程

1、多线程共享一个进程地址空间

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

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

1、同步

(1)概念

同步指的是多个任务(线程)按照约定的顺序相互配合完成一件事情

由信号量来决定线程是继续还是阻塞

(2)初始化信号量

头文件

#include <semaphore.h>

函数原型

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

函数参数:

sem:初始化的信号量

pshared:信号量共享范围(0:线程间使用   非0:进程间使用)

value:信号量初值

返回值:

成功:0

失败:-1

P/V操作:(参数为信号量sem)

sem_wait( ):P操作 -1

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

sem_post( ):V操作 +1

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

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

2、互斥

(1)概念

引入互斥锁的目的是用来保证共享数据操作(临界资源)的完整性

互斥锁主要用来保护临界资源

每个临界资源都要有一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源

线程必须先获取互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止

(2)创建互斥锁步骤

a.创建锁

类型    锁名

pthread_mutex_t  myMutex;

b.初始化锁

头文件:

#include <pthread.h>

函数原型:

int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexarr_t *attr)

函数参数:

mutex:互斥锁

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

返回值:

成功:0

失败:-1

c.上锁

头文件:

#include <pthread.h>

函数原型:

int pthread_mutex_lock(pthread_mutex_t *mutex)

函数参数:

mutex :互斥锁

返回值:

成功:0

失败:-1

d.解锁

头文件:

#include <pthread.h>

函数原型:

int pthread_mutex_unlock(pthread_mutex_t *mutex)

函数参数:

mutex :互斥锁

返回值:

成功:0

失败:-1

e.销毁锁

头文件:

#include<pthread.h>


函数原型:

int pthread_mutex_destroy(pthread_mutex_t *mutex)

函数参数:

mutex:要销毁的锁

返回值:

成功:0

失败:-1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值