一、线程的概念
对于进程来说,进程创建时间和空间成本较高,因为进程是承担分配系统资源的基本实体,所以线程的出现就成为了必然。Linux线程与进程非常相似,Linux设计者在设计之初觉得如果再为线程设计数据结构和调度算法就会使整个系统变得十分复杂。地址空间和地址空间上的虚拟地址,本质就是一种资源,所以线程就在进程的基础上让不同的线程看到进程地址空间上的代码区中的不同代码,并让不同线程去执行不同的代码,这样同样也实现了线程的功能。不同的线程在同一个进程地址空间中运行,共享相同的内存和其他资源。所以说线程是进程内部的一个执行分支,线程也是CPU调度的基本单位。
每个线程都有一个与之关联的task_struct结构体,该结构体包含了线程的状态信息、调度信息、资源使用情况等。CPU在执行调度的时候,根本就不用区分进程和线程,反正你们都有task_struct结构体,都是执行流。Linux中所有的可调度执行流都叫做轻量级进程。
二、详谈页表以及虚拟地址到物理地址之间的转化
磁盘中的数据块和内存中的数据块都是4kb大小。为了管理内存中每一块4kb大小的内存块,操作系统内核中会有描述该内存块的结构体,4GB的内存大小中就有1048576个内存块,再利用数组就能将整个内存管理起来,对于内存的管理就变成了对于数组的增删查改。所以,操作系统进行内存管理的基本单位就是4kb。
再谈页表,其实页表并不是只有一块数据结构,而是分为了页目录,页表。一个虚拟地址有32位,32位中的前10位用来表示处于页目录中的哪一个位置,页目录中最多可以存放 个页表的首地址,第11到20位用来表示处于页表的哪一个页表项,一个页表中最多也有 个页表项,一个页表项就对应内存中的一个4kb大小的数据块。最后12位用来确定数据块中的字节, 刚好就是4kb,正好可以确定是哪一个字节,所以后12位也叫做页内偏移。
如果再在页表项中加入访问权限审查,有没有被使用过等等标志位,就能对用户操作进行一些审查以及拦截,来保证内存访问的安全性。
针对页表的这一种分页存储的模式,我们所写的函数编译完就是一块一块的虚拟地址,根据函数名就可以找到函数入口地址,各个函数就分配得到了不同的虚拟地址,也就是得到了不同的页表区域。同样的,我们只要给不同的线程分配不同的页表区域,就可以让不同的线程能够访问到页表的不同子集,进一步的,就可以让不同的线程访问到不同的代码,从而实现多线程技术。
三、创建线程的小例子
thread:这是一个指向pthread_t类型的指针,用于获取新创建线程的线程ID。在调用pthread_create后,这个指针会被设置为新线程的ID。
attr:这是一个指向pthread_attr_t类型的指针,用于设置线程的属性,如栈大小、优先级等。如果这个参数为NULL,那么线程将使用默认的属性。通常情况下,如果你不需要设置特殊的线程属性,可以传递NULL。
start_routine:这是一个函数指针,指向线程开始执行时要调用的函数。这个函数通常被称为线程的“入口点”或“启动例程”。
arg:用于向线程的启动例程传递参数。你可以通过这个参数向线程传递任何类型的数据。如果你的启动例程不需要任何参数,可以传递NULL。
3.1、主函数
#include <iostream> #include <unistd.h> #include <pthread.h> using namespace std; void* newthread(void*) { while (true) { cout << 22222222 << endl; sleep(1); } } int main() { pthread_t pthread; pthread_create(&pthread, nullptr, newthread, nullptr); while(true) { cout << 11111111 << endl; sleep(1); } return 0; }
3.2、makefile
myThread:testThread.cc g++ -o $@ $^ -std=c++11 -lpthread .PHONY:clean clean: rm -f myThread
可以看到两个线程就同时跑起来了: