欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > 8.1IO进程线程

8.1IO进程线程

2024/10/25 0:35:17 来源:https://blog.csdn.net/2301_81236242/article/details/140838362  浏览:    关键词:8.1IO进程线程

笔记

进程

一.多进程引入

1.1引入目的

程序员写程序时,一个程序可能由多个任务组成,如果使用的是单进程,或单任务,那么该任务执行阻塞时,其他任务就无法执行,必须等到该任务解除阻塞后,才能去执行其他任务。

多进程或多线程,可以解决同一个程序中多个任务并发执行的情况

1.2进程的概念

        1>进程是程序的一次执行过程

        2>进程是程序资源分配的基本单位,系统会分给每个进程分配4G的虚拟内存,分为0--3G的用户空间和3--4G的内核空间

                多个进程共享内核空间,用户空间相互独立

        3>进程是一个动态的过程,有生命周期的概念,分为创建态、就绪态、运行态、阻塞态、死亡态

        4>进程在内核空间中存储在一个名叫task_struct中的结构体中(PCB)

task_struct:

进程描述符:task struct包含了描述一个进程所需的所有信息。
进程状态:包括运行、就绪、阻塞等状态。
进程标识符:如进程ID(PID)。
进程调度信息:如优先级、调度策略等。
内存管理信息:如虚拟地址空间、页表等。
文件系统信息:如打开的文件、文件系统根目录等
信号处理:包括待处理信号和信号处理函数。
进程间通信:如消息队列、共享内存等IPC机制相关信息。
时间和定时器:如进程创建时间、CPU使用时间等。
线程信息:在Linux中,线程被视为轻量级进程,也用taskstruct表示。

        5>单核cpu处理多任务时,一般使用的是时间片轮询机制

        6>进程与程序的区别:程序是静态的,是存储在磁盘上的二进制代码

        7>进程是动态的,是有生命周期的7>进程的组成:进程控制块(PCB)、数据段、程序段

1.3 进程的种类

进程一共分为三大类:交互进程、批处理进程、守护进程

        1>交互进程:他是由shel控制,用于直接跟用户进行交互的进程。例如:vim编辑器、文本编辑器

        2>批处理进程:本质上维护了一个队列,被放入队列中的进程会统一被调度。例如gcc编译器的一步到位的编译

        3>守护进程:脱了了终端而存在的进程,随着系统的启动而开始,随着系统的结束而终止。例如:服务进程3

1.4 进程号的概念

每个进程在系统中都有一个唯一的标识位,用一个整数表示,这就是该进程的进程号(PID)

        1>PID(processID):当前进程的进程号

        2>PPID(parent process lD):当前进程的父进程的进程号

每个进程都是由其父进程进行拷贝赋值出来的。

可以进入 /proc目录中的每个数字都是现在正在执行的一个进程

1.5 特殊的进程
        

        1>0号进程:也成为 idel进程,他是操作系统启动后执行的第一个进程,这个进程也叫空闲进

程,当没有其他进程执行时,系统会默认执行该进程。1号进程和2号进程都是由0号进程创建出来

的。

        2>1号进程:也称init进程,该进程由0号进程产生,主要完成系统创建时一些软件硬件的初始化

工作。当其他进程的父进程死亡后,会托管其子进程

        3>2号进程:也称kthreadd,该进程由0号进程产生,也成为调度进程,当某个就绪进程时间片

轮到时,该进程负责进程的调度

工作

        4>孤儿进程:当前进程的父进程死亡后,但是当前进程还没有结束,那么当前进程称为孤儿进

程,孤儿进程会由1号进程收养57僵尸进程:当前进程已经死亡,但是其父进程没有为其收尸,那么

该进程为僵尸进程

1.6 进程的相关指令

        1>查看进程信息的命令:ps,跟不同的选项,执行不同的状态

                ps-ef:显示进程之间的关系

                ps-ajx:显示进程的状态

                ps-aux:可以查看进程资源使用情况

        2>top或htop        可以动态展示进程的占用情况 

        3>pstree:展示进程树,可以显示进程的父子关系

        4>查看给定进程的进程号:pidof 进程名ki:向进程发送信号,发信号的格式

                        ki -信号名(号)进程号

                        能够发送的信号号有:可以通过指令kil-l查看

1.7进程的状态

        1>主要状态一共有五个:创建态、就绪态、运行态、阻塞态、死亡态

        2>程序中的进程的状态显示:可以通过指令man ps查看进程的状态

                进程的状态由两部分组成:主状态和附加态

主状态
                D                不可中断的休眠态(usually I0)
                R                运行态(on run queue)
                S                可中断的体眠态(waiting for an event to complete)
                T                暂停态,会给出作业号进行控制
               W                已经弃用
                X                死亡态(should never be seen)
                Z                僵尸态

附加态

                <                高优先级的进程(not nice to other users)

                N                低优先级的进程(nice to other users)

                L                锁到内存中的进程(for real-time and custom Io)

               S                会话组组长,默认为当前终端

                I                包含多线程的进程(using CLONE_THREAD,like NPTL pthreads do)

               +                表示是前台运行的进程

        3>进程状态切换的实例

二.多进程编程

        多线程允许使用同一个线程体函数

2.1 fork进程的创建函数

        1> 在进程的创建过程,其实就是通过拷贝父进程的task struct结构体而来的,子进程中保存

了大部分的父进程的遗传基因,只需要修改少部分的内容,如子进程的进程号,子进程的父进程

号。

        2>子进程和父进程的资源用户空间是完全独立的,创建出子进程后,父子进程的资源相互独立,

互不影响

        3>进程的创建,通过fork函数完成

        4>当子进程创建后,会跟父进程一起执行fork后面的语句

#include<sys/types.h>

#include<unisted.h>

pid_t fork(void)

功能:拷贝父进程得到子进程

参数:无

返回值:成功时,父进程中返回子进程中的pid号,子进程中返回0,失败返回-1并置位错误码

注意:创建出的子进程跟父进程没有先后执行的顺序,遵循时间片轮询机制

2.2进程号的获取

#include<sys/types.h>

#include<unsitd.h>

pid_t getpid(void)

功能:获取当前进程的进程号

参数:无

返回值:当前进程的pid

pid_t getppid(void)

功能:获取当前进程的父进程的pid号

参数:无

返回值:当前进程的父进程的pid

2.3进程的退出(exit、_exit)

#include<stdlib.h>

void exit(int status);

功能:退出当前进程,并刷新当前进程打开的标准IO文件指针的缓冲区

参数:退出时的状态

        EXIT_SUCCESS:成功退出

        EXIT_FAILURE:失败退出

返回值:无

#include<stdlib.h>

void _exit(int status);

功能:退出当前进程,但是不刷新当前进程打开的标准IO文件指针的缓冲区

参数:退出时的状态

        EXIT_SUCCESS:成功退出

        EXIT_FAILURE:失败退出

返回值:无

2.4进程资源的回收

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int *wstatus)

功能:阻塞回收子进程的资源,如果没有进程退出,那么就阻塞等待

参数:子进程退出时的状态,一般不接受,直接填NULL即可

返回值:成功返回回收的子进程的pid,失败返回-1并置位错误码

#include<sys/types.h>

 

#include<sys/wait.h>

pid_t waitpid(pid_t pid,int *wststus,int options)

功能:既可以阻塞也可以非阻塞形式回收僵尸进程

参数1:要回收的进程号

                >0:表示回收具体的某个进程

                =0:能够回收当前进程所在的进程组中的任意一个子进程

                =-1:回收任意一个子进程

                <-1:回收别的进程组(进程组id为给定的pid的绝对值)中的任意一个子进程

参数2:子进程退出时的状态,一般不接收,直接填NULL即可

参数3:表示是否阻塞回收僵尸进程

        0:表示阳塞        

        WNOHANG:表示非阳塞

返回值:

        >0如果成功回收了子进程资源,那么会返回该子进程的pid

        =0:表示以非阳塞的形式回收资源,但是没有回收到子进程

        =-1:失败返回-1并置位错误码

2.5创建三个进程

可以让父进程创建一个子进程,再由父进程或者子进程创建一个子进程

2.6写时拷贝技术

2.7特殊进程的验证

        1>僵尸进程

#include<myhead.h>int main(int argc, const char *argv[])
{pid_t pid = fork();      //创建出子进程if(pid < 0){perror("fork error");return -1;}else if(pid == 0){printf("我是子进程\n");exit(EXIT_SUCCESS);         //退出子进程}//父进程内容printf("我是父进程\n");sleep(5);return 0;
}

        2>孤儿进程

#include<myhead.h>int main(int argc, const char *argv[])
{pid_t pid = fork();      //创建出子进程if(pid < 0){perror("fork error");return -1;}else if(pid > 0){printf("我是父进程\n");exit(EXIT_SUCCESS);         //退出子进程}//父进程内容printf("我是子进程\n");sleep(5);return 0;
}

        3>创建守护进程

#include <myhead.h>int main(int argc, const char *argv[])
{pid_t pid=fork();if(pid<0){perror("fork error");return -1;}else if(pid>0){//退出父进程exit(EXIT_SUCCESS);}//将自己设置成会话组组长setsid();//更改操作目录为根目录chdir("/");//获得创建文件的最大权限umask(0);//将标准输入输出出错重定向到long.txt文件中int fd =open("./long.txt",O_WRONLY|O_CREAT|O_APPEND,0664);if(-1==fd){perror("open error");return -1;}//重定向dup2(fd,STDIN_FILENO);dup2(fd,STDOUT_FILENO);dup2(fd,STDERR_FILENO);//执行操作while(1){printf("hello!\n");fflush(stdout);sleep(1);}close(fd);return 0;
}

线程

一.多线程基本概念

        1> 线程:也称为轻量版的进程(LWP),是更小的任务执行单元,是进程的一个执行路径

        2> 线程是任务器调度的最小单位

        3> 一个进程中可以包含多个线程,多个线程共享进程的资源。

        4> 线程几乎不占用资源,只是占用了很少的用于程序状态的资源(大概有8k左右)

        5> 由于多个线程共同使用进程的资源,导致,线程在操作上容易出现不安全的状态

        6> 线程操作开销较小、任务切换效率较高

        7> 一个进程中,至少要包含一个线程(主线程)

        8> 在有任务执行漫长的IO等待过程中,可以同时执行其他任务

        9> linux中不直接支持线程相关的支持库,需要引入第三方库,线程支持库

                sudo apt-get install manpages-posix manpages-posix-dev

                如果程序中使用的线程支持库中的函数,编译程序时,需要加上 -lpthread 选项

二.线程支持函数(多线程编程)

多线程允许使用同一个线程体函数

2.1pthread_create:创建线程
 #include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);功能:在当前进程中,创建一个分支线程参数1:用于接收创建好的线程ID参数2:线程的属性,一般填NULL表示使用系统默认的属性创建线程参数3:线程体函数,需要传递一个函数,参数为void*,返回值为void*参数4:参数3的参数返回值:成功创建返回0,失败返回一个错误码,注意,不是内核提供的错误码   
#include<myhead.h>//定义一个用于传递数据的结构体类型
struct Buf
{int num;double key;
};//定义线程体函数
void *task(void *arg)
{int num = (*((struct Buf*)arg)).num;double key = ((struct Buf*)arg)->key;while(1){printf("我是分支线程, num = %d, key=%.2lf\n", num, key);sleep(1);}
}/************************主程序*****************************/
int main(int argc, const char *argv[])
{int num = 520;       //定义整形变量double key = 1314;   //定义浮点型数据struct Buf buf = {num, key};    //封装要传递的数据//定义变量存储线程号pthread_t tid = -1;//创建分支线程if(pthread_create(&tid, NULL, task, &buf) != 0){printf("pthread_create error\n");return -1;}printf("我是主线程,tid = %#lx\n", tid);//while(1);getchar();return 0;
}
2.2pthread_self:线程号的获取
       #include <pthread.h>pthread_t pthread_self(void);功能:获取当前线程的线程号参数:无返回值:当前线程的线程号
2.3pthread_exit:线程退出函数
       #include <pthread.h>void pthread_exit(void *retval);功能:退出当前的线程参数:线程退出时的状态,一般填NULL返回值:无
2.4pthread_jion:线程资源回收函数
  #include <pthread.h>int pthread_join(pthread_t thread, void **retval);功能:阻塞等待给定线程的退出,并回收该线程的资源参数1:要回收的线程号参数2:接收线程退出时的状态返回值:成功返回0,失败返回错误码
2.5pthread_detach:线程分离太态
       #include <pthread.h>int pthread_detach(pthread_t thread);功能:将线程设置成分离态,设置了分离态的线程,退出后,由系统回收其资源参数:要被分离的线程id号返回值:成功返回0,失败返回错误码
2.6 pthread_setcancelstate:设置取消属性
     #include <pthread.h>int pthread_setcancelstate(int state, int *oldstate);功能:设置是否接收取消指令参数1:新的状态PTHREAD_CANCEL_ENABLE:可接受状态PTHREAD_CANCEL_DISABLE:不可接收状态参数2:线程的旧的状态容器,如果不愿意要之前的状态,填NULL即可返回值:成功返回0,失败返回错误码
   返回值:成功返回0,失败返回错误码#include<myhead.h>//定义线程体函数
void *task(void *arg)
{//设置线程不可取消状态pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);printf("我是分支线程,线程号为:%#lx\n", pthread_self());//exit(EXIT_SUCCESS);        //退出进程int count = 0;while(1){printf("我真的还想再活五百年。。。。\n");sleep(1);count++;if(count == 10){break;}}pthread_exit(NULL);           //退出线程
}/************************主程序*****************************/
int main(int argc, const char *argv[])
{//定义变量存储线程号pthread_t tid = -1;//创建分支线程if(pthread_create(&tid, NULL, task, NULL) != 0){printf("pthread_create error\n");return -1;}printf("我是主线程,tid = %#lx, 主线程线程号:%#lx\n", tid, pthread_self());/*回收分支线程的资源if(pthread_join(tid, NULL) == 0){printf("成功回收了%#lx的资源\n", tid);}*///将线程设置成分离态pthread_detach(tid);printf("线程已经分离\n");//休眠5秒sleep(5);//向分之线程中发送一个取消请求pthread_cancel(tid);//while(1);getchar();return 0;
}

作业

使用两个线程完成两个文件的拷贝,分支线程1拷贝前一半,分支线程2拷贝后一半,主线程回收两个分支线程的资源

#include <myhead.h>
//定义数据结构体
typedef struct data
{const char *argv1;const char *argv2;int size;int len;
}Data;
//定义复制函数
void* copy(void *data)
{//将数据结构体中的值取出const char*argv1=((Data*)data)->argv1;const char*argv2=((Data*)data)->argv2;int size=((Data*)data)->size;int len=((Data*)data)->len;int fp=-1;//打开被复制文件if((fp=open(argv1,O_RDONLY))==-1){perror("open argv[1] error");return NULL;}int fd=-1;//打开准复制文件if((fd=open(argv2,O_WRONLY|O_CREAT|O_TRUNC,0664))==-1){return NULL;}//移动光标到指定位置lseek(fp,size,SEEK_SET);lseek(fd,size,SEEK_SET);char temp=0;//按给定长度提取字符并写入到文件中for (int i=0; i<len;i++){read(fp,&temp,sizeof(temp));write(fd,&temp,sizeof(temp));}//关闭文件close(fp);close(fd);printf("拷贝成功!\n");}
int main(int argc, const char *argv[])
{//判断传入的文件是否为三个if(argc!=3){perror("file input error");return-1;}//打开被复制文件int fp =-1;if((fp=open(argv[1],O_RDONLY))==-1){perror("open argv[1] error");return-1;}//计算文件大小int size=lseek(fp,0,SEEK_END);//分支线程复制长度int len=size-size/2;//关闭文件close(fp);//定义传入函数的数据变量,不能只定义一个变量,因为传入函数的数据是地址传递,两个线程要调用两次函数,如果一个线程修改变量,另一个也会修改Data data1;Data data2;data1.argv1=argv[1];data1.argv2=argv[2];//分支线程复制位置的值赋给数据结构体data1.size=size/2;//分支线程复制长度的值赋给数据结构体data1.len=len;//创建分支线程pthread_t tid=-1;if(pthread_create(&tid,NULL,copy,&data1)!=0){perror("pthread error");return -1;}//主线程复制需要的数据的值赋给数据结构体data2.argv1=argv[1];data2.argv2=argv[2];data2.size=0;data2.len=size/2;//调用复制函数copy(&data2);//阻塞回收分支线程资源pthread_join(tid,NULL);return 0;
}

思维导图

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com