笔记
进程
一.多进程引入
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;
}