Linux 进程

进程和轻量级进程

image-20211112165309944

在Linux内核中,进程/线程对应的数据结构是task_struct,定义在include/linux/sched.h中。

线程在Linux中的实现是 Naive POSIX Thread Library 。在内核眼中,Linux的线程实际上也是一个进程(task_struct),区别是线程的“进程”共享了地址空间、文件描述符等,称作==轻量级进程==。因此,Linux的线程也是独立的调度单元,是可以分别在不同的CPU上同时运行的。原生的Linux 线程(2.6版本内核之前)因为只实现在了用户态,所以,即使是一个多线程程序,对于内核来说只能看到一个进程,这些线程就只能在一个CPU上运行,对于多核多线程来说是很致命的。

从实现的角度,Linux的线程(LWP)是通过pthread库创建/使用的。而进程和线程的创建都调用了clone()系统调用(kernel/fork.c )。区别是两者使用了不同的flags。

进程管理

进程状态

  • TASK_RUNNING: The process is either executing on a CPU or waiting to be executed
  • TASK_INTERRUPTIBLE: The process is suspended (sleeping) until some condition becomes true.
  • TASK_UNINTERRUPTIBLE: Like TASK_INTERRUPTIBLE, except that delivering a signal to the sleeping process leaves its state unchanged.

PID

PID用来区分不同的进程结构体。Linux中最大PID数目可以在/proc/sys/kernel/pid_max中查看。

每个进程/轻量级进程都分配有一个唯一的PID。但是对于同一进程中的线程来说,我们拿到的是进程ID确是相同的,这是怎么实现的呢?

Linux为了兼容POSIX标准,利用了线程组(thread group)这一概念。所有的线程都会把线程组里面第一个线程的PID存在tgid字段内。==getpid()系统调用返回的实际上是tgid的值。==

/**
 * sys_getpid - return the thread group id of the current process
 *
 * Note, despite the name, this returns the tgid not the pid.  The tgid and
 * the pid are identical unless CLONE_THREAD was specified on clone() in
 * which case the tgid is the same in all threads of the same group.
 *
 * This is SMP safe as current->tgid does not change.
 */
SYSCALL_DEFINE0(getpid)
{
    return task_tgid_vnr(current);
}

bitmap管理PID

IDR管理PID

  • PID: replace pid bitmap implementation with IDR API commit, commit

进程切换

==TLDR:进程在调用schedule()方法时,将当前进程运行的寄存器信息保存在task_struct->thread_info内,同时从进程B中的task_struct->thread_info中加载B运行时的寄存器信息。==

正念冥想

正念是什么

与事物的现实性达成一致,我们可以通过拥抱它们来接受它们,在一刻一刻的觉知里。

正念不是一个概念,而是一种与生命保持明智关系的方式,在生命唯一的当下展开。

正念的态度因素

如果想要长期地实践正念,并将正念融入到生活当中,那么我们需要关注一些正念的态度因素。对于这些正念的态度因素,最主要的是让这些概念融入到脑内,心里,而并非是强制遵守它。记住即可。

不做评判

不做评判并不意味着我们不去对事件品头论足,而是说==意识到在做评判,并且有目的地不陷入到评判中,这本身就是一种美丽而又温柔的修习。==从某种意义上来说,这种修习的目的是让我们==足够关心自己以及我们所处的环境,从而不会有那种瞬间崩溃,也不会拘泥于喜欢或者不喜欢,想要或者不想要。==归根结底,当我们做出一个评判时,实际上我们就成为了它的囚徒。因为当事情发生时,评判降低了我们应对挑战的可能性。我们会困在一个观点中,而这个观点可能并不是一个完整地故事,完整地视角(盲人摸象)。

==关键点:不被自己地喜欢和厌恶所禁锢。把自己从下意识去评判“好”与“恶”这个习惯中解救出来,从自己的欲望和恐惧中解救出来。==

耐心

当我们失去耐心时,受害者往往是与我们生活在一起的人,我们最爱的人,还有和我们一起工作的人。

我们都希望朝着一个真正变化的、有益的方向前进。但如禅语所说,==“you can’t push the river”==,不能逆势而为。为了到达某个地方而逼自己加快脚步是不明智的,因为这样往往会让自己忘记了真正的目的。

耐心是我们可以培养的一种非常可贵的品质。特别是在我们当下这个时代,它显得愈发重要。在这个电子信息时代,我们被电子产品吸引,甚至对其上瘾。我们的社会变成了一个点击型社会,所有在手机或者电脑上呈现的东西,逐步变得愈来愈“快餐”和“标题党”。它们通过社交媒体分发给我们,给我们想要的东西。这些“快餐”或者朋友圈的“点赞”实际上触发了我们大脑中神经元的连接,刺激多巴胺的释放,在这一瞬间会给人你一种非常非常满足的感觉。我们会因为朋友圈的一个“点赞”获得极大的满足感,但是这只持续了不到一秒,然后呢?我们会想要更多。我们开始观察社交媒体的生态,然后变得愈发不耐烦,这就是上瘾的一种表现,而我们甚至对此都毫无觉察。

所以耐心实际上培养了对这种事情的免疫力。因为当我们意识到有那种想要得到什么的冲动时,我们可以让自己慢下来(延迟满足)。==所以在一整天里,不断地觉察到自己有多么不耐烦,这便能帮助我们培养耐心。==

我们不必强迫自己变得耐心,而是去关注那些真的陷进去的时刻,强烈地想要去得到某个结果,然后引发出一系列蝴蝶效应般的结果,最后我们再也无法被满足(成瘾)。

初学者心态

In the beginner’s mind there are many possibilities, in the expert’s mind, there are few.

—— Suzuki Roshi(铃木俊隆)

==在某种意义上,我们所知道地一切都会妨碍我们看到那里的一些东西。因为我们是在透过我们的有色眼镜看面前的东西。==原有的知识结构,可能会演变为严重的问题。因为它会限定你,让你在已知的领域中对那些新兴未知的苗头视而不见,而那才是你想要发现的,全新的,前所未见的东西。

信任

将信任带入修习实际上是在提醒自己有很多我们可以信任的东西,就比如我们可以相信我们的呼吸正在i体内穿梭,我们的脚将带着我们前行,我们的心脏仍会在今晚继续跳动。这些都是在提醒我们在当下这个时刻发生的事情中,有多少是可信的,是真正值得信任的。 最重要的是,我们的心是否值得信赖。==唯一能让自己的新值得信赖的方法就是相信自己,相信自己值得信赖,即便是我们把注意力放在了可能辜负自己或者他人信任的事情上==。

信任是深刻的,象征洞察力的源泉。它包括如何将内在与外在的经历发散开,使其保持良好的关联。

不争

不争不是反对积极。**不争可能会要你去做更多的事情——不依附于结果,不去拼命让结果显得更好,而是确保当下正在做的每一件都具有完整性,然后水到渠成。**现实中我们会有DDL,会有一些杂七杂八的事情,看起来和不争的概念相悖。但是当把不争带到我们的意识前,让我们专注于当下,实际上可以帮助我们更快地完成工作。

当你的目标非常明确的时候,不争是很有效的。当我们太执着于胜利的结果时,可能会得不到很好的结果,因为心浮气躁。不争是将我们对胜利的渴望剥离出来,这并不意味着我们会成为一个消极的人,总是一事无成。

接纳

接纳意味着看到事物的现实,承认并接受它们就是这样的。 接纳并不意味着我们不能努力去改变它们,接纳意味着看清现实,意味着你有时会意识到一些不可接受的事情。然后的问题是,我们该如何与这些不可容忍的事情,需要改变的事情保持明智的关系?这和消极的顺从一点关系都没有。

==我们需要看到真实的现实,正在发生的一切,看到它的复杂性以及简洁性。看得清楚,然后开始行动。接纳意味着对事物的现状有深刻洞察的认识,如果你是唯一一个看清这件事情走向的人,相信自己。==

Go channel 实现

Go的Channel在runtime里面是一个hchan的结构体,每次我们make一个新的channel时,runtime从heap内分配一个hchan结构体管理channel。

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}
  • qcount:当前channel queue内的element数目,当qcount等于dataqsiz时,表示channel buffer已经满了,send channel会被阻塞;
  • dataqsiz:channel的buffer大小,也就是我们在make时设定的值。dataqsiz在channel创建后不会再变动,因此channel的buffer是不会动态扩容的;
  • buf:buffered channel缓存elements的内存地址。是一个数组实现的环形队列;
  • elemsize:element类型大小;
  • closed:channel是否已经被关闭,防止关闭已经关闭的channel;
  • elemtype:channel element 类型;
  • sendx:buffer中下一个生产的element的index;
  • recvx:buffer中下一个消费的element的index;
  • recvq:阻塞的接收channel,是一个sudog的链表
  • sendq:阻塞的发送channel,是一个sudog的链表

当buffer没有满时,send channel发送elements时直接把elements放在buf内,增加sendx和qcount;当buffer满时,send channel被阻塞,并加入到sendq链表内,等待被唤醒;

Rust: Concurrency

为什么Rust不做green threading

Send 和 Sync

Rust中几乎所有的并发特性都是标准库或者第三方库提供的,真正由Rust语言本身提供的很少。而std::marker中的traits SendSync算是其中一个。

**实现Send的类型值的所有权可以在线程间传递。**Rust中绝大多数类型都实现了Send,但也有些例外,如Rc<T>:因为如果将Rc<T>的拷贝值的所有权在多个线程中传递,Rust无法保证Rc<T>引用值的正确性。

实现Sync的类型表示该类型值可以在多个线程中被引用。也就是说,如果&T实现了Send,那么类型T就是Sync

SendSync markers其实就是将其他语言中的一些潜规则显式地标明出来,让编译器提前检查出代码中的隐患。

线程原语

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

thread::spawn新建一个线程,执行传递的闭包函数,返回一个JoinHandler,可以在主线程中调用join等待子线程结束。move用于强制闭包获取它使用的变量的所有权。

我们可以看看thread::spawn的实现:

#[stable(feature = "rust1", since = "1.0.0")]
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
    F: FnOnce() -> T,
    F: Send + 'static,
    T: Send + 'static,
{
    Builder::new().spawn(f).expect("failed to spawn thread")
}

spawn的入参和返回值都实现Send,同时其生命周期为'static。这是因为在多线程中,每个线程的执行周期不是同步的。父线程或者子线程的结束都会导致入参或者返回值的生命周期不满足Rust的约束条件。

Rust:Generic and Traits

泛型

Rust 泛型会在编译时根据参数将泛型单态化(Monomorphization ),因此,Rust 泛型在运行时是没有任何损耗的。

泛型在函数定义

fn largest<T: std::cmp::PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 43, 15];
    println!("the largest number is {}", largest(&number_list));

    let char_list = vec!['y', 'm', 'c', 'd'];
    println!("the largest char is {}", largest(&char_list));
}

泛型在结构体定义

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let p = Point{x: 5, y: 3};
    println!("x is {}, y is {}", p.x, p.y);

    let p = Point{x: 5.0, y: 3.0};
    println!("x is {}, y is {}", p.x, p.y)
}

需要注意,这里表示结构体内x和y是同一种类型,如果需要x和y类型不同,需要定义两个泛型参数。