进程的一生:
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();