创建了一个进程,他进程的图大概如下

如果我们再创建新的进程
我们出现新的,下面图片的tasks truct(PCB),进程地址空间(PCD),页表等等,如果我们进行了程序替换操作系统还会帮我们把代码look到物理内存,帮我们代码和物理内存的建立映射关系

那么fork之后,父子是共享代码打的,可以if else 判断,让父子进程执行不同的代码块,等于不同的执行流,可以做到对特定资源的划分,因为进程具有独立性,所以我把进程2给清除掉,也不会影响到进程1
那么我们可以创建进程,我们创建PCB,但它们不具有单独的进程地址空间和页表,它们指向的是同一个进程地址空间

那么上面三个PCB,分别占用进程低地址空间的一小部分代码和数据,页表也分别有一小部分占用,那么这个执行流就叫线程,所以线程也是进程内部运行的执行流
线程和进程的关系是 n比1的,所以操作系统要管理线程,先描述在组织,所以就有了TCB,这里TCB和PCB有一个理念的问题,如果他是真正实现了真线程的平台,比如Windows,那么他就要设计进程和线程的结构体,并且很多线程在进程内部,你还要维护它们之间的关系,这会导致它们的耦合性变得特别高,会对于维护之间变得成本特别高,为什么他要这么设计,因为设计他的人,他认为进程和线程在执行流之间是不一样的,而设计linux的人他认为没有进程没有线程,他认为只有一个叫做执行流,因为他认为进程只是比线程的代码和数据多一些,线程只是比进程的代码和数据少一些,所以觉得没有区别,linux的线程使用PCB模拟的,这里有个问题,linux有没有TCB,答案是有的,因为他就是PCB,就是说TCB和PCB就是一回事,只是他没有专门设计TCB,他只是复用了PCB,用它来表示执行流的概念
那么从cpu的来看到的所有的task_struct 都是一个进程
那么从cpu的来看到的所有的task_struct 都是一个执行流(线程)
以前我们以为进程只是task_strruct,那么我们重新来了解下进程
进程 = 内核数据结构 + 进程对应的代码和数据
进程 = 内核视角 + 承担分配系统资源的基本实体
进程 = 向系统申请资源的基本单位
内部只有一个进程 = 单执行流进程
内部有多个进程 = 多执行流进程
cpu通过查找物理内存的代码的地址是虚拟地址如果是32位系统下,他的虚拟地址数是32位的,虚拟地址到物理内存不是直接转换,而是划分为 10 10 12
虚拟地址的前十位,存在页目录(页目录只有一个)
虚拟地址中间十位 存在1级页表
虚拟地址的后十二位 做页内偏移量,能覆盖页内的所有地址

根据虚拟地址前十位在页目录做映射找到1级页表,再根据虚拟地址中间十位,搜索页表,再二级页表找到相对应的物理内存,再通过虚拟地址的后十位位,做偏移量,找到你想要的数据,进行读取
这样做有什么好处?
1.进程虚拟地址空间管理和内存管理,通过页表+page进程解耦,也就是说不关心页内的细节,只关心page在不在
2.页表分离了,可以实现页表的按序获取,比如页目录有的条目他是暂时不用的,那么我们等用的时候再创建这个页表,不用的时候不创建,用的时候再创建,所以等于节省空间
创建一个新线程的代价要比创建一个新进程小得多
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多
能充分利用多处理器的可并行数量
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作
1.性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变。
2.健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3.缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4.编程难度提高
编写与调试一个多线程程序比单线程程序困难得多
代码
makefile
mythread:mythread.cc
g++ -o $@ $^ -lpthread -std=c++11
.PHONY:
clean:
rm -f mythread
mythread.cc
#include
#include
#include
#include
using namespace std;
static void printTid(const char*name,const pthread_t &tid)
{
printf("%s 正在运行,thread id: 0x%x\n", name ,tid);
}
void* startRoutine(void *args)
{
const char* name = static_cast<const char*>(args);
int cnt = 5;
while(true)
{
printTid(name, pthread_self());//主线程
sleep(1);
if(!(cnt--))
{
break;
}
}
cout << "线程退出了"<<endl;
return nullptr;
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
sleep(10);
// 线程退出的时候,一般必须要进行join,如果不进行join,就会
// 造成类似于进程那样的内存泄露问题
pthread_join(tid,nullptr);
while(true)
{
printTid("main thread", pthread_self());//主线程
sleep(1);
}
return 0;
}
上面代码是是创建线程,下面图片是输入命令kill -19退出进程,可以看到进程退出了二个线程也退出了

如果运行kill -18命令他也会跑

我在代码cnt–的里面加了个野指针,看到下面的图可以看到线程出现异常,整个进程退出,
所以线程异常等于进程异常

是一个输出型参数&