进程的一生:

execute:
exec族
 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),
 子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的
 用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建
 新进程,所以调用exec前后该进程的id并未改变。
 其实有六种以exec开头的函数,统称exec函数:
 vector
 ls -l -i list 
 execl("/bin/ls","-l","-i",NULL);
 execlp("ls","-l","-i",NULL);
#include <unistd.h>
       int exec l(const char *path, const char *arg, ...);
        int exec lp(const char *file, const char *arg, ...);
        int exec le(const char *path, const char *arg,..., char * const envp[]);
        int exec v(const char *path, char *const argv[]);
        int exec vp(const char *file, char *const argv[]);
        int exec vpe(const char *file, char *const argv[], char *const envp[]);
(1).带l vs 带 v
     int execl  (const char *path, const char *arg, ...); //list   ...表示任何可变参数
     int execv  (const char *path, char *const argv[]);//vector ---数组

 功能:
             执行一个文件 (可执行文件 a.out / 1.sh )
 image 映像/镜像
 text|data|bss|堆 栈|环境变量及命令行参数 + pcb
    用新进程的 镜像 替换 调用进程的 镜像    替换之后身份未变,还是bash的子进程
 参数:
           @path   要执行的文件的路径(包含可执行文件的名字)
           eg:
               ls       
               /bin/ls 
           @arg   表示 可执行文件的文件名 (命令)
             ls  
              ... 可变参数 
          ls -l / 
          最后写一个NULL 表示结束 
返回值:成功不会返回,失败返回-1.
 execl("/bin/ls","ls","-l","/",NULL);

区别:
     在于,参数传递的方式不同,
     l --- list ---参数逐个列举 
     eg:
      execl("/bin/ls","ls","-l","/",NULL);
     v ---vector --- 参数组织成 指针数组的形式 
     eg:
       char *const arg[] = {"ls","-l","/",NULL};
       execv("/bin/ls",arg);
       
(2). p vs e
int execl p (const char *file, const char *arg, ...);
 int execv p (const char *file, char *const argv[]);
p --- path ->PATH (环境变量 --- 都是可执行文件的路径) // echo $PATH 查看环境变量
带p 表示可执行文件的寻找方式,是从系统的环境变量PATH中的路径下面去找


如何用execlp/execvp方式启动自己的程序。
         将自己写的程序所在的路径,添加到系统的环境变量中即可。
         export PATH=$PATH:/home/linux/2021-code/3month/2-proc/proc/exec //注意,先引用原有环境变量 再追加
int execl  e (const char *path, const char *arg,...,   char * const envp[]);
 int execvp e(const char *file, char *const argv[]  ,   char * const envp[]);
带e 表示的是可以给要执行的 新程序 传递需要的 环境变量
extern char **environ; //系统的环境变量信息 的指针数组的首地址
env 可以看环境变量






练习:
实现
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
	char s[100] = {0};
	while(1)
	{
		printf("minishell$ ");
		fgets(s,sizeof(s),stdin);
		s[strlen(s) - 1] = '\0';
		
		if(strncmp(s,"quit",4) == 0 || strncmp(s,"exit",4) == 0)
		{
			printf("-----exit-----\n");
			return 0;
		}
		char *p[10] = {NULL};
		int i = 0;
		p[i] = strtok(s," ");
		while(p[++i] = strtok(NULL," "))
			;	
		pid_t pid = fork();
		if(pid < 0)
		{
			perror("fork fail");
			return -1;
		}
		if(pid > 0)
		{
			int status;
			printf("father -----\n");
			wait(NULL);
		}
		else if(pid == 0)
		{
			if(execvp(p[0],p) < 0);
			{
				perror("execvp fail");
				return -1;
			}
			//exit(99);
		}
	}
	return 0;
} 
 
进程的总结:

线程thread :
并发量的问题(并发程度)

进程的特点:
a.父子进程的独立空间
b.
线程:
区别:
        进程 -是资源分配的基本单位
         线程 -系统调度的最小单位
线程是CPU执行的最下单位 //就是来干活的
线程是轻量级的进程,进程是重量级的进程--需要大量的资源的分配
线程不需要太多的资源
{
线程pid
程序计数器 (pc - process counter)
相关寄存器
栈的空间
}
进程是分配资源和调度执行的基本单位。
        fork
         分配资源 --- 进程 (获取系统系统资源)
         调度执行 --- 线程 (侧重执行任务)
         线程 因为 共享 进程的资源
         避免了 大量资源的开辟
         线程的创建效率 高于进程创建效率
线程的缺点
安全性较差,---
线程间的通信。 ---共享进程资源
关系:
1.线程是存在进程中的
2.线程共用的进行的各个段和相关资源
线程的函数:
NPTL(Native Posix Thread Library) 线程库
1.生命周期:
(1)线程的创建:
pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                     void *(*start_routine) (void *), void *arg);
         功能:该函数可以创建指定的一个线程。
参数:
                  @thread 线程id,需要实现定义并由该函数返回。
                   @attr   线程属性,一般是NULL,表示默认属性。(可结合性+分离属性)
                   @start_routine 
                         指向指针函数的函数指针。
                           本质上是一个函数的名称即可。
                         称为回调函数,是线程的执行空间。
                   @arg  回调函数的参数,即参数3的指针函数参数。
             返回值:  成功 0
                             失败 错误码
练习:创建两个子线程,同时操作1个变量,一个+1,一个+2
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
//线程的执行函数 -回调函数 
void * doSomething1(void *arg)
{
	int i = 0;
	int *p = arg;
	while (1)
	{
		(*p)++;
		printf("1 doSomething n = %d i = %d\n",*p,i++);
		sleep(1);
	}
	return NULL;
}
void *doSomething2(void *arg)
{
	int i = 0;
	int *p = arg;
	while (1)
	{
		printf("2 doSomething n = %d i = %d\n",(*p)+=2,i++);
		sleep(1);
	}
	return NULL;
}
#define N 2
typedef void *(*threadF_t) (void*);
int main(int argc, const char *argv[])
{
	int n = 10;
	
	pthread_t tid[N]; //long 
	///threadF_t pFunc[] = {doSomething1,doSomething2,NULL};
	
	int i = 0;
	for (i = 0; i < N; ++i)
	{
		//int ret = pthread_create(&tid[i],NULL,pFunc[i],&n);
		int ret = pthread_create(&tid[i],NULL,doSomething1,&n);
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			return -1;
		}
	}
#if 0
	int ret = pthread_create(&tid1,NULL,doSomething1,&n);
	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;
	}
	
	pthread_t tid2; //long 
	ret = pthread_create(&tid2,NULL,doSomething2,&n);
	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;
	}
#endif
	//printf("tid = %ld\n",tid);
	//while(1);
	while (1)
	{
		printf("main n = %d\n",n);
		sleep(1);
	}
	return 0;
} 
 
int pthread_attr_init
int pthread_attr_init(pthread_attr_t *attr);
功能:
@初始化一个attr的变量
参数:
@ attr,需要变量来接受初始值
返回:
0 成功,
非0 错误;
int pthread_attr_destroy
int pthread_attr_destroy(pthread_attr_t *attr);
功能:销毁attr变量。
参数: @attr,属性变量
返回:
0 成功,
非0 错误;
int pthread_attr_setdetachstate
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);(这个风险不大就是恨麻烦)
功能:把一个线程设置成相应的属性
参数:@attr,属性变量,有init函数初始化他。
@detachstate:有2个可选值
1、PTHREAD_CREATE_DETACHED 可分离 //设置好线程的初始状态,可分离就不用主线程去回收了
2、PTHREAD_CREATE_JOINABLE 可结合
设置分离属性。
返回值:成功 返回 0
失败 >0 ,以及错误号
int pthread_detach(pthread_t thread);
这个写在create下面,创建成功下面,但是也有风险,就怕分离前子线程执行完了,才去分离已经没用了,也可以直接放在子线程开头
功能,设置分离属性
参数,线程id号,填自己的id
设置为可分离状态时,主线程就可以干自己的事情,不用自己用join来回收(join能获得子线程的状态,关心其回收状态看无人机),由系统来回收
创建线程时就设置了初始状态可分离

(2)线程的执行:
回调函数------执行任务
(3)线程的结束:
线程的结束条件:
The new thread terminates in one of the following ways:
       * It  calls  pthread_exit(3),            //pthread_exit([status])
          specifying  an  exit  status  value that is available to
          another thread in the same process that calls pthread_join(3).
       * It returns from start_routine().          //return 
          This is equivalent to calling pthread_exit(3)  with
          the value supplied in the return statement.
       * It is canceled (see pthread_cancel(3)).         //被取消 
        * Any of the threads in the process calls exit(3),        //exit() --- 进程正常结束 
          or the main thread performs a return from main()       .//main- return 意味着就是进程结束。  
          This causes the termination of all threads in the process.
结束的函数
pthread_exit()
        void pthread_exit(void *retval);     // 与return 的效果一致  
                 功能:子线程自行退出
                 参数: retval 线程退出时候的返回状态,临死遗言。
                 返回值:无
//若在主线程中调用,表示主线程结束,但是,此时进程空间不销毁,直到,所有的子线程都结束只有,此时进程空间销毁。
pthread_exit(值所在空间得地址) 这个值可以是数字和字符串
pthread_join()
int pthread_join(pthread_t thread, void **retval);
void pthread_exit(void *retval); //退出时,将"要传递的退出的状态值"的地址 传递  
 int pthread_join(pthread_t thread, void **retval);//等待 接收 pthread_exit()  传递过来的 状态值的地址保存   
pthread_exit pthread_join (类似exit和wait)
pthread_datach(tid)
int pthread_detach(pthread_t thread);
将线程分离,不用主线程来join了
pthread_cancel()
强制退出 ==》他杀  ==》主线程结束子线程
         int pthread_cancel(pthread_t thread);
         功能:请求结束一个线程
         参数:thread 请求结束一个线程tid
         返回值:成功 0
                       失败 -1;
若在主线程中调用来结束子线程,则需传入子线程的tid,若在子线程中调用来结束主线程也相同。
练习:用线程 无人机 回收线程
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h> 
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>
#define N 4
void *ctrl(void *arg)
{
	int i = 0;
	while(i < 3)
	{
		printf("---ctrl----\n");
		sleep(1);
		++i;
	}
	pthread_exit("ctrl");
}
void *video(void *arg)
{	
	int i = 0;
	while(i < 4)
	{
		printf("---video----\n");
		sleep(1);
		++i;
	}
	pthread_exit("video");
}
void *trans(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---trans----\n");
		sleep(1);
		++i;
	}
	pthread_exit("trans");
}
void *store(void *arg)
{
	int i = 0;
	while(i < 6)
	{
		printf("---store----\n");
		sleep(1);
		++i;
	}
	pthread_exit("store");
}
typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{
	pthread_t tid[N];
	threadF_t pFunc[] = {ctrl,video,trans,store,NULL};
	int i = 0;
	for(;i < N;++i)
	{
		int ret = pthread_create(&tid[i],NULL,pFunc[i], NULL);
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			//return -1;
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}
	}
	void *retval;
	int j = 0;
	for(;j < N;++j)
	{
		pthread_join(tid[j],&retval);
		printf("-----%s  exit------\n",(char *)retval);
	}
	printf("----------------main----exit-----------------\n");
	return 0;
} 
 
void pthread_cleanup_push(void (*routine)(void *), void *arg);
功能:注册一个线程清理函数(第一个是调一个自己写的清理函数,里面可以写要销毁的东西,比如用来分离的属性,第二个是清理函数要传的参数)
参数, @routine,线程清理函数的入口
@arg,清理函数的参数。
返回值,无
void pthread_cleanup_pop(int execute);
功能:调用清理函数
@execute,
非0 执行清理函数
0,不执行清理
但是关闭就会触发如果它放在关闭后面,这时候0和1都没关系
返回值,无
这两个必须一起用他们的本质的宏,各自完成do{(clean_push)
所以这里面在预定义的时候会替换,注意一定要符合语法
}while的一半(clean_pop)
注:写了pthread_cleanup_push就必须写 pthread_cleanup_pop,否则会报错!。

触发方式: 以下方式都会触发cleanup
1.pthread_cleanup_pop(非零值)
2.pthread_cleanup_pop(0) // pthread_exit() 退出动作会导致 触发
pthread_exit(NULL); //线程正常结束
pthread_cleanup_pop(0); //放在pthread_exit()后,就算是0也会执行清理。
3.pthread_cancel();: // 线程异常结束 被其他线程结束时
为什么需要cleanup呢?
因为在初始化线程attr参数时,创建了一些资源,线程分离自己回收后,设置attr的资源也要需要清理,故要调用cleanup函数。
练习:
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h> 
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>
#define N 4
void *ctrl(void *arg)
{
	int i = 0;
	while(i < 5)
	{
		printf("---ctrl----\n");
		sleep(1);
		++i;
	}
	pthread_exit("ctrl");
}
void *video(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---video----\n");
		sleep(1);
		++i;
	}
	pthread_exit("video");
}
void *trans(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---trans----\n");
		sleep(1);
		++i;
	}
	pthread_exit("trans");
}
void *store(void *arg)
{
	int i = 0;
	while(i < 5)
	{
		printf("---store----\n");
		sleep(1);
		++i;
	}
	pthread_exit("store");
}
void cleanup(void *arg)
{
	pthread_attr_destroy(arg);
	printf("cleanup\n");
}
typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{
	pthread_t tid[N];
	threadF_t pFunc[] = {ctrl,video,trans,store,NULL};
	int i = 0;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	for(;i < N;++i)
	{
		int ret = pthread_create(&tid[i],&attr,pFunc[i], NULL);  //创建线程时就设置好分离状态
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			//return -1;
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}
		//pthread_detach(tid[i]);
	}
	pthread_cleanup_push(cleanup,&attr);	
	while(1)
	{	
		printf("----------------main----exit-----------------\n");
		sleep(1);
	}
	pthread_cleanup_pop(0);
	pthread_exit(NULL);
	return 0;
} 
 
 
多线程的互斥机制
临界资源: 共享资源
临界区 : 一段代码区域(访问临界资源的那段代码)
原子操作: 要么不操作,要操作,一定是一次完整的操作。不能被打断。

概念:
互斥 ===》在多线程中对临界资源的排他性访问。
互斥机制 ===》互斥锁 ===》保证临界资源的访问控制。
框架:
定义互斥锁-》初始化锁-》加锁-》解锁-》销毁
1、定义:
pthread_mutex_t mutex;如果定义在main函数要传参,全局大家都能用
2、//初始化一把锁 pthread_mutex_init();
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
功能:
将已经定义好的互斥锁初始化。
参数:
@mutex 要初始化的互斥锁
@atrr
初始化的值,一般是NULL表示默认锁 //读写锁 ,自旋锁 ()
返回值:
成功 0
失败 非零
3、 //上锁 pthread_mutex_lock();
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:用指定的互斥锁开始加锁代码
加锁后的代码到解锁部分的代码属于原子操作,
在加锁期间其他进程/线程都不能操作该部分代码
如果该函数在执行的时候,mutex已经被其他部分
使用则代码阻塞。
参数: @mutex 用来给代码加锁的互斥锁
返回值:成功 0
失败 非零
4、//解锁 pthread_mutex_unlock();
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:将指定的互斥锁解锁。
解锁之后代码不再排他访问,一般加锁解锁同时出现。
参数:用来解锁的互斥锁
返回值:成功 0
失败 非零
5、//销毁一把锁 pthread_mutex_destroy();
代码示例:
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
int count;
pthread_mutex_t mutex;
void*doSth1(void *arg)
{
	int i = 0;
	while (i < 50000)
	{
		pthread_mutex_lock(&mutex);
		int temp = count;
		printf("count 1 = %d\n",count);
		count = temp + 1;
		pthread_mutex_unlock(&mutex);
		++i;
	}
	return NULL;
}
void* doSth2(void *arg)
{
	int i = 0;
	while (i < 50000)
	{	
		pthread_mutex_lock(&mutex);
		int temp = count;
        printf("count 2 = %d\n",count);
		count = temp + 1;
		pthread_mutex_unlock(&mutex);
		++i;
	}
	return NULL;
}
int main(int argc, const char *argv[])
{
	pthread_t tid[2];
	pthread_mutex_init(&mutex,NULL); //此时 有了一把锁 
	int ret = pthread_create(&tid[0],NULL,doSth1,NULL);
	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;
	}
	 ret = pthread_create(&tid[1],NULL,doSth2,NULL);
	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;
	}
	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
} 
练习:

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>
#define N 2
int cnt;
pthread_mutex_t mutex;
void *do_write1(void *arg)
{
	int i = 0;
	while(i < 100)
	{	
		pthread_mutex_lock(&mutex);
		fprintf(arg,"thread1 cnt = %d\n",cnt);
		fflush(arg);
		++i,++cnt;
		pthread_mutex_unlock(&mutex);
	}
}
void *do_write2(void *arg)
{
	int i = 0;
	while(i < 100)
	{
		pthread_mutex_lock(&mutex);
		fprintf(arg,"thread2 cnt = %d\n",cnt);
		fflush(arg);
		++i,++cnt;
		pthread_mutex_unlock(&mutex);
	}
}
void cleanup(void *arg)
{
	pthread_attr_destroy(arg);
	printf("cleanup\n");
}
typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{
	if(argc != 2)
	{
		printf("Usage : %s <filename> ",argv[0]);
		return -1;
	}
	FILE *fp = fopen(argv[1],"w+");	
	
	if(fp == NULL)
	{
		perror("open fail");
		return -1;
	}
	int i = 0;
	pthread_t tid[N];
	threadF_t pFunc[] = {do_write1,do_write2,NULL};
	pthread_mutex_init(&mutex,NULL);
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	for(i;i < N;++i)
	{
		int ret = pthread_create(&tid[i],&attr,pFunc[i], fp);
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}
	}
	pthread_mutex_destroy(&mutex);	
	pthread_cleanup_push(cleanup,&attr);	
	pthread_cleanup_pop(1);
	
	pthread_exit(NULL);
	fclose(fp);
	return 0;
} 
 
Tips:
一、断错误处理:
![]()
1、gcc <filename> -g
2、gdb ./执行文件
3、在gdb下运行 输入r即可。

二、获得当前的tid
pthread_self();



















