当前位置:首页 » 操作系统 » linux内核共享

linux内核共享

发布时间: 2022-12-30 04:55:01

linux系统的内存管理知识详解

内存是Linux内核所管理的最重要的资源之一。内存管理系统是操作系统中最为重要的部分,因为系统的物理内存总是少于系统所需要的内存数量。虚拟内存就是为了克服这个矛盾而采用的策略。系统的虚拟内存通过在各个进程之间共享内存而使系统看起来有多于实际内存的内存容量。Linux支持虚拟内存, 就是使用磁盘作为RAM的扩展,使可用内存相应地有效扩大。核心把当前不用的内存块存到硬盘,腾出内存给其他目的。当原来的内容又要使用时,再读回内存。以下就是我为大家整理到的详细LINUX系统内存管理的知识,欢迎大家阅读!!!

LINUX系统教程:内存管理的知识详解

一、内存使用情况监测

(1)实时监控内存使用情况

在命令行使用“Free”命令可以监控内存使用情况

代码如下:

#free

total used free shared buffers cached

Mem: 256024 192284 63740 0 10676 101004

-/+ buffers/cache: 80604 175420

Swap: 522072 0 522072

上面给出了一个256兆的RAM和512兆交换空间的系统情况。第三行输出(Mem:)显示物理内存。total列不显示核心使用的物理内存(通常大约1MB)。used列显示被使用的内存总额(第二行不计缓冲)。 free列显示全部没使用的内存。Shared列显示多个进程共享的内存总额。Buffers列显示磁盘缓存的当前大小。第五行(Swap:)对对换空间,显示的信息类似上面。如果这行为全0,那么没使用对换空间。在缺省的状态下,free命令以千字节(也就是1024字节为单位)来显示内存使用情况。可以使用—h参数以字节为单位显示内存使用情况,或者可以使用—m参数以兆字节为单位显示内存使用情况。还可以通过—s参数使用命令来不间断地监视内存使用情况:

#free –b –s2

这个命令将会在终端窗口中连续不断地报告内存的使用情况,每2秒钟更新一次。

(2)组合watch与 free命令用来实时监控内存使用情况:

代码如下:

#watch -n 2 -d free

Every 2.0s: free Fri Jul 6 06:06:12 2007

total used free shared buffers cached

Mem: 233356 218616 14740 0 5560 64784

-/+ buffers/cache: 148272 85084

Swap: 622584 6656 615928

watch命令会每两秒执行 free一次,执行前会清除屏幕,在同样位置显示数据。因为 watch命令不会卷动屏幕,所以适合出长时间的监测内存使用率。可以使用 -n选项,控制执行的频率;也可以利用 -d选项,让命令将每次不同的地方显示出来。Watch命令会一直执行,直到您按下 [Ctrl]-[C] 为止。

二、虚拟内存的概念

(1)Linux虚拟内存实现机制

Linux虚拟内存的实现需要六种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制、内存共享机制。

首先内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址,在用户程序运行时如果发现程序中要用的虚地址没有对应的物理内存时,就发出了请求页要求;如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。 如果没有足够的内存可供分配,那么就调用交换机制,腾出一部分内存。另外在地址映射中要通过TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中后也要修改页表来映射文件地址。

(2)虚拟内存容量设定

也许有人告诉你,应该分配2倍于物理内存的虚拟内存,但这是个不固定的规律。如果你的物理保存比较小,可以这样设定。如果你有1G物理内存或更多的话,可以缩小一下虚拟内存。Linux会把大量的内存用做Cache的,但在资源紧张时回收回.。你只要看到swap为0或者很小就可以放心了,因为内存放着不用才是最大的浪费。

三、使甩vmstat命令监视虚拟内存使用情况

vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存、进程、CPU活动进行监视。它是对系统的整体情况进行统计,不足之处是无法对某个进程进行深入分析。通常使用vmstat 5 5(表示在5秒时间内进行5次采样)命令测试。将得到一个数据汇总它可以反映真正的系统情况。

代码如下:

#vmstat 5 5

procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----

r b swpd free buff cache si so bi bo in cs us sy id wa

1 0 62792 3460 9116 88092 6 30 189 89 1061 569 17 28 54 2

0 0 62792 3400 9124 88092 0 0 0 14 884 434 4 14 81 0

0 0 62792 3400 9132 88092 0 0 0 14 877 424 4 15 81 0

1 0 62792 3400 9140 88092 0 0 0 14 868 418 6 20 74 0

1 0 62792 3400 9148 88092 0 0 0 15 847 400 9 25 67 0

vmstat命令输出分成六个部分:

(1)进程procs:

r:在运行队列中等待的进程数 。

b:在等待io的进程数 。

(2)内存memoy:

swpd:现时可用的交换内存(单位KB)。

free:空闲的内存(单位KB)。

buff: 缓冲去中的内存数(单位:KB)。

cache:被用来做为高速缓存的内存数(单位:KB)。

(3) swap交换页面

si: 从磁盘交换到内存的交换页数量,单位:KB/秒。

so: 从内存交换到磁盘的交换页数量,单位:KB/秒。

(4) io块设备:

bi: 发送到块设备的块数,单位:块/秒。

bo: 从块设备接收到的块数,单位:块/秒。

(5)system系统:

in: 每秒的中断数,包括时钟中断。

cs: 每秒的环境(上下文)切换次数。

(6)cpu中央处理器:

cs:用户进程使用的时间 。以百分比表示。

sy:系统进程使用的时间。 以百分比表示。

id:中央处理器的空闲时间 。以百分比表示。

如果 r经常大于 4 ,且id经常小于40,表示中央处理器的负荷很重。 如果bi,bo 长期不等于0,表示物理内存容量太小。

四、Linux 服务器的内存泄露和回收内存的方法

1、内存泄漏的定义:

一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

2、内存泄露的危害

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的`是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。存在内存泄漏问题的程序除了会占用更多的内存外,还会使程序的性能急剧下降。对于服务器而言,如果出现这种情况,即使系统不崩溃,也会严重影响使用。

3、内存泄露的检测和回收

对于内存溢出之类的麻烦可能大家在编写指针比较多的复杂的程序的时候就会遇到。在 Linux 或者 unix 下,C、C++语言是最使用工具。但是我们的 C++ 程序缺乏相应的手段来检测内存信息,而只能使用 top 指令观察进程的动态内存总额。而且程序退出时,我们无法获知任何内存泄漏信息。

使用kill命令

使用Linux命令回收内存,我们可以使用Ps、Kill两个命令检测内存使用情况和进行回收。在使用超级用户权限时使用命令“Ps”,它会列出所有正在运行的程序名称,和对应的进程号(PID)。Kill命令的工作原理是:向Linux操作系统的内核送出一个系统操作信号和程序的进程号(PID)。

应用例子:

为了高效率回收内存可以使用命令ps 参数v:

代码如下:

[root@www ~]# ps v

PID TTY STAT TIME MAJFL TRS DRS RSS %MEM COMMAND

2542 tty1 Ss+ 0:00 0 8 1627 428 0.1 /sbin/mingetty tty1

2543 tty2 Ss+ 0:00 0 8 1631 428 0.1 /sbin/mingetty tty2

2547 tty3 Ss+ 0:00 0 8 1631 432 0.1 /sbin/mingetty tty3

2548 tty4 Ss+ 0:00 0 8 1627 428 0.1 /sbin/mingetty tty4

2574 tty5 Ss+ 0:00 0 8 1631 432 0.1 /sbin/mingetty tty5

2587 tty6 Ss+ 0:00 0 8 1627 424 0.1 /sbin/mingetty tty6

2657 tty7 Ss+ 1:18 12 1710 29981 7040 3.0 /usr/bin/Xorg :0 -br -a

2670 pts/2 Ss 0:01 2 682 6213 1496 0.6 -bash

3008 pts/4 Ss 0:00 2 682 6221 1472 0.6 /bin/bash

3029 pts/4 S+ 0:00 2 32 1783 548 0.2 ping 192.168.1.12

3030 pts/2 R+ 0:00 2 73 5134 768 0.3 ps v

然后如果想回收Ping命令的内存的话,使用命令:

代码如下:

# Kill -9 3029

使用工具软件

Memprof是一个非常具有吸引力且非常易于使用的软件,它由Red Hat的Owen Talyor创立。这个工具是用于GNOME前端的Boehm-Demers-Weiser垃圾回收器。这个工具直接就可以执行,并且其工作起来无需对源代码进行任何修改。在程序执行时,这个工具会以图形化的方式显示内存的使用情况。

相关介绍:Linux

严格来讲,Linux这个词本身只表示Linux内核,但人们已经习惯了用Linux来形容整个基于Linux内核,并且使用GNU 工程各种工具和数据库的操作系统。

Linux拥有以下特性:类似于Unix的基本思想,支持完全免费与自由传播,完全兼容POSIX1.0标准,支持多用户、多任务、有着良好的界面、支持多种平台。Linux 能运行主要的UNIX工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

Linux有着许多不同的版本,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、视频游戏控制台、台式计算机、大型机和超级计算机。

② 什么是linux内核

Linux是由Linus Torvalds开发的类UNIX的操作系统,Linux主要特点是开源的,因此我们可以免费使用来当做服务器。

Linux严格分为两个含义。

1.广泛的Linux是指Linux发行版

2.狭义的Linux是指Linux内核

Linux内核是操作系统的基础,介于硬件和软件之间,并且内核位于操作系统中,操作系统将在硬件和软件之间进行调解,Linux内核是操作系统核心部分的功能。

二:内核的基本性能

1.流程管理

在Linux内核中,程序的执行状态以进程为单位进行管理。此外,内核为每个进程准备一个名为task_stract结构的数据结构。

2.进程调度程序

可执行状态等待哪个进程以哪个顺序执行,由于基本上不可能运行比CPU数量更多的进程,因此运行过程的效率非常重要。

3.内存管理

在Linux内核中,使用物理内存和虚拟内存管理数据。通过分配对应于物理存储器的虚拟地址,而不是实际为每个进程分配物理存储器地址,可以使用容量远大于实际物理存储器容量的存储器。它使它成为可能。此外,由于每个进程都分配了自己的虚拟地址,因此每个进程的内存空间是独立的,并且不会违反其他进程的内存。

3.文件系统

它以文件的形式提供存储数据的访问方法。所有数据都以文件的形式进行管理。/ Directory(根目录)作为顶点,内核本身作为文件和目录的集合存在。

③ linux为什么一定要把内核空间划分到高1G

Linux虚拟内存的大小为2^32(在32位的x86机器上),内核将这4G字节的空间分为两部分。最高的1G字节(从虚地址
0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而较低的3G字节(从虚地址0x00000000到
0xBFFFFFFF),供各个进程使用,称为“用户空间”。因为每个进程可以通过系统调用进入内核,因此,Linux内核空间由系统内的所有进程共享。
于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟地址空间(也叫虚拟内存).

每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。最高的1GB内核空间则为所有进程以及内核所共享。另外,进程的“用户空间”也叫“地址空间”,在后面的叙述中,我们对这两个术语不再区分。

用户空间不是进程共享的,而是进程隔离的。每个进程最大都可以有3GB的用户空间。一个进程对其中一个地址的访问,与其它进程对于同一地址的访问绝不冲
突。比如,一个进程从其用户空间的地址0x1234ABCD处可以读出整数8,而另外一个进程从其用户空间的地址0x1234ABCD处可以读出整数
20,这取决于进程自身的逻辑。
因此Linux对用户空间与内核空间的划分起到了一定程度上的冲突避免。

④ linux内核同步问题

Linux内核设计与实现 十、内核同步方法

手把手教Linux驱动5-自旋锁、信号量、互斥体概述

== 基础概念: ==

并发 :多个执行单元同时进行或多个执行单元微观串行执行,宏观并行执行

竞态 :并发的执行单元对共享资源(硬件资源和软件上的全局变量)的访问而导致的竟态状态。

临界资源 :多个进程访问的资源

临界区 :多个进程访问的代码段

== 并发场合: ==

1、单CPU之间进程间的并发 :时间片轮转,调度进程。 A进程访问打印机,时间片用完,OS调度B进程访问打印机。

2、单cpu上进程和中断之间并发 :CPU必须停止当前进程的执行中断;

3、多cpu之间

4、单CPU上中断之间的并发

== 使用偏向: ==

==信号量用于进程之间的同步,进程在信号量保护的临界区代码里面是可以睡眠的(需要进行进程调度),这是与自旋锁最大的区别。==

信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。它负责协调各个进程,以保证他们能够正确、合理的使用公共资源。它和spin lock最大的不同之处就是:无法获取信号量的进程可以睡眠,因此会导致系统调度。

1、==用于进程与进程之间的同步==

2、==允许多个进程进入临界区代码执行,临界区代码允许睡眠;==

3、信号量本质是==基于调度器的==,在UP和SMP下没有区别;进程获取不到信号量将陷入休眠,并让出CPU;

4、不支持进程和中断之间的同步

5、==进程调度也是会消耗系统资源的,如果一个int型共享变量就需要使用信号量,将极大的浪费系统资源==

6、信号量可以用于多个线程,用于资源的计数(有多种状态)

==信号量加锁以及解锁过程:==

sema_init(&sp->dead_sem, 0); / 初始化 /

down(&sema);

临界区代码

up(&sema);

==信号量定义:==

==信号量初始化:==

==dowm函数实现:==

==up函数实现:==

信号量一般可以用来标记可用资源的个数。

举2个生活中的例子:

==dowm函数实现原理解析:==

(1)down

判断sem->count是否 > 0,大于0则说明系统资源够用,分配一个给该进程,否则进入__down(sem);

(2)__down

调用__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);其中TASK_UNINTERRUPTIBLE=2代表进入睡眠,且不可以打断;MAX_SCHEDULE_TIMEOUT休眠最长LONG_MAX时间;

(3)list_add_tail(&waiter.list, &sem->wait_list);

把当前进程加入到sem->wait_list中;

(3)先解锁后加锁;

进入__down_common前已经加锁了,先把解锁,调用schele_timeout(timeout),当waiter.up=1后跳出for循环;退出函数之前再加锁;

Linux内核ARM构架中原子变量的底层实现研究

rk3288 原子操作和原子位操作

原子变量适用于只共享一个int型变量;

1、原子操作是指不被打断的操作,即它是最小的执行单位。

2、最简单的原子操作就是一条条的汇编指令(不包括一些伪指令,伪指令会被汇编器解释成多条汇编指令)

==常见函数:==

==以atomic_inc为例介绍实现过程==

在Linux内核文件archarmincludeasmatomic.h中。 执行atomic_read、atomic_set这些操作都只需要一条汇编指令,所以它们本身就是不可打断的。 需要特别研究的是atomic_inc、atomic_dec这类读出、修改、写回的函数。

所以atomic_add的原型是下面这个宏:

atomic_add等效于:

result(%0) tmp(%1) (v->counter)(%2) (&v->counter)(%3) i(%4)

注意:根据内联汇编的语法,result、tmp、&v->counter对应的数据都放在了寄存器中操作。如果出现上下文切换,切换机制会做寄存器上下文保护。

(1)ldrex %0, [%3]

意思是将&v->counter指向的数据放入result中,并且(分别在Local monitor和Global monitor中)设置独占标志。

(2)add %0, %0, %4

result = result + i

(3)strex %1, %0, [%3]

意思是将result保存到&v->counter指向的内存中, 此时 Exclusive monitors会发挥作用,将保存是否成功的标志放入tmp中。

(4) teq %1, #0

测试strex是否成功(tmp == 0 ??)

(5)bne 1b

如果发现strex失败,从(1)再次执行。

Spinlock 是内核中提供的一种比较常见的锁机制,==自旋锁是“原地等待”的方式解决资源冲突的==,即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地“打转”(忙等待)。由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 —— 自旋锁不应该被长时间的持有(消耗 CPU 资源),一般应用在==中断上下文==。

1、spinlock是一种死等机制

2、信号量可以允许多个执行单元进入,spinlock不行,一次只能允许一个执行单元获取锁,并且进入临界区,其他执行单元都是在门口不断的死等

3、由于不休眠,因此spinlock可以应用在中断上下文中;

4、由于spinlock死等的特性,因此临界区执行代码尽可能的短;

==spinlock加锁以及解锁过程:==

spin_lock(&devices_lock);

临界区代码

spin_unlock(&devices_lock);

==spinlock初始化==

==进程和进程之间同步==

==本地软中断之间同步==

==本地硬中断之间同步==

==本地硬中断之间同步并且保存本地中断状态==

==尝试获取锁==

== arch_spinlock_t结构体定义如下: ==

== arch_spin_lock的实现如下: ==

lockval(%0) newval(%1) tmp(%2) &lock->slock(%3) 1 << TICKET_SHIFT(%4)

(1)ldrex %0, [%3]

把lock->slock的值赋值给lockval;并且(分别在Local monitor和Global monitor中)设置独占标志。

(2)add %1, %0, %4

newval =lockval +(1<<16); 相当于next+1;

(3)strex %2, %1, [%3]

newval =lockval +(1<<16); 相当于next+1;

意思是将newval保存到 &lock->slock指向的内存中, 此时 Exclusive monitors会发挥作用,将保存是否成功的标志放入tmp中。

(4) teq %2, #0

测试strex是否成功

(5)bne 1b

如果发现strex失败,从(1)再次执行。

通过上面的分析,可知关键在于strex的操作是否成功的判断上。而这个就归功于ARM的Exclusive monitors和ldrex/strex指令的机制。

(6)while (lockval.tickets.next != lockval.tickets.owner)

如何lockval.tickets的next和owner是否相等。相同则跳出while循环,否则在循环内等待判断;

* (7)wfe()和smp_mb() 最终调用#define barrier() asm volatile ("": : :"memory") *

阻止编译器重排,保证编译程序时在优化屏障之前的指令不会在优化屏障之后执行。

== arch_spin_unlock的实现如下: ==

退出锁时:tickets.owner++

== 出现死锁的情况: ==

1、拥有自旋锁的进程A在内核态阻塞了,内核调度B进程,碰巧B进程也要获得自旋锁,此时B只能自旋转。 而此时抢占已经关闭,(单核)不会调度A进程了,B永远自旋,产生死锁。

2、进程A拥有自旋锁,中断到来,CPU执行中断函数,中断处理函数,中断处理函数需要获得自旋锁,访问共享资源,此时无法获得锁,只能自旋,产生死锁。

== 如何避免死锁: ==

1、如果中断处理函数中也要获得自旋锁,那么驱动程序需要在拥有自旋锁时禁止中断;

2、自旋锁必须在可能的最短时间内拥有

3、避免某个获得锁的函数调用其他同样试图获取这个锁的函数,否则代码就会死锁;不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个锁,如果试图这么做,系统将挂起;

4、锁的顺序规则(a) 按同样的顺序获得锁;b) 如果必须获得一个局部锁和一个属于内核更中心位置的锁,则应该首先获取自己的局部锁 ;c) 如果我们拥有信号量和自旋锁的组合,则必须首先获得信号量;在拥有自旋锁时调用down(可导致休眠)是个严重的错误的;)

== rw(read/write)spinlock: ==

加锁逻辑:

1、假设临界区内没有任何的thread,这个时候任何的读线程和写线程都可以键入

2、假设临界区内有一个读线程,这时候信赖的read线程可以任意进入,但是写线程不能进入;

3、假设临界区有一个写线程,这时候任何的读、写线程都不可以进入;

4、假设临界区内有一个或者多个读线程,写线程不可以进入临界区,但是写线程也无法阻止后续的读线程继续进去,要等到临界区所有的读线程都结束了,才可以进入,可见:==rw(read/write)spinlock更加有利于读线程;==

== seqlock(顺序锁): ==

加锁逻辑:

1、假设临界区内没有任何的thread,这个时候任何的读线程和写线程都可以键入

2、假设临界区内没有写线程的情况下,read线程可以任意进入;

3、假设临界区有一个写线程,这时候任何的读、写线程都不可以进入;

4、假设临界区内只有read线程的情况下,写线程可以理解执行,不会等待,可见:==seqlock(顺序锁)更加有利于写线程;==

读写速度 CPU > 一级缓存 > 二级缓存 > 内存 ,因此某一个CPU0的lock修改了,其他的CPU的lock就会失效;那么其他CPU就会依次去L1 L2和主存中读取lock值,一旦其他CPU去读取了主存,就存在系统性能降低的风险;

mutex用于互斥操作。

互斥体只能用于一个线程,资源只有两种状态(占用或者空闲)

1、mutex的语义相对于信号量要简单轻便一些,在锁争用激烈的测试场景下,mutex比信号量执行速度更快,可扩展

性更好,

2、另外mutex数据结构的定义比信号量小;、

3、同一时刻只有一个线程可以持有mutex

4、不允许递归地加锁和解锁

5、当进程持有mutex时,进程不可以退出。

• mutex必须使用官方API来初始化。

• mutex可以睡眠,所以不允许在中断处理程序或者中断下半部中使用,例如tasklet、定时器等

==常见操作:==

struct mutex mutex_1;

mutex_init(&mutex_1);

mutex_lock(&mutex_1)

临界区代码;

mutex_unlock(&mutex_1)

==常见函数:==

=

⑤ linux两种共享内存的区别

持久性不同、文件反射方式不同。
1、持久性不同。sysvshm是持久化的,除非被一个进程明确的删除,否则它始终存在于内存里,直到系统关机。mmap映射的内存在不是持久化的,假如进程关闭,映射随即失效,除非事前已经映射到了一个文件上。
2、文件反射方式不同。前者用COW的方式,把文件映射到当前的进程空间,修改操作不会改动源文件。后者直接把文件映射到当前的进程空间,所有的修改会直接反应到文件的page cache,而后由内核自动同步到映射文件上。

⑥ linux内核如何勾选共享内存

所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。所有进程都能访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会即时被有访问同一段共享内存的其他进程看到。共享内存的使用大大降低了在大规模数据处理过程中内存的消耗,但是共享内存的使用中有很多的陷阱,一不注意就很容易导致程序崩溃。

1.超过共享内存的大小限制?

在一个linux服务器上,共享内存的总体大小是有限制的,这个大小通过SHMMAX参数来定义(以字节为单位),您可以通过执行以下命令来确定 SHMMAX 的值:

# cat /proc/sys/kernel/shmmax

如果机器上创建的共享内存的总共大小超出了这个限制,在程序中使用标准错误perror可能会出现以下的信息:

unable to attach to shared memory

>解决方法:

1、设置 SHMMAX

SHMMAX 的默认值是 32MB 。一般使用下列方法之一种将 SHMMAX 参数设为 2GB :

通过直接更改 /proc 文件系统,你不需重新启动机器就可以改变 SHMMAX 的默认设置。我使用的方法是将以下命令放入 />etc/rc.local 启动文件中:

echo "2147483648" > /proc/sys/kernel/shmmax

您还可以使用 sysctl 命令来更改 SHMMAX 的值:sysctl -w kernel.shmmax=2147483648

最后,通过将该内核参数插入到 /etc/sysctl.conf 启动文件中,您可以使这种更改永久有效:echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf

2、设置 SHMMNI

⑦ 一文搞懂 , Linux内核—— 同步管理(下)

上面讲的自旋锁,信号量和互斥锁的实现,都是使用了原子操作指令。由于原子操作会 lock,当线程在多个 CPU 上争抢进入临界区的时候,都会操作那个在多个 CPU 之间共享的数据 lock。CPU 0 操作了 lock,为了数据的一致性,CPU 0 的操作会导致其他 CPU 的 L1 中的 lock 变成 invalid,在随后的来自其他 CPU 对 lock 的访问会导致 L1 cache miss(更准确的说是communication cache miss),必须从下一个 level 的 cache 中获取。

这就会使缓存一致性变得很糟,导致性能下降。所以内核提供一种新的同步方式:RCU(读-复制-更新)。

RCU 解决了什么

RCU 是读写锁的高性能版本,它的核心理念是读者访问的同时,写者可以更新访问对象的副本,但写者需要等待所有读者完成访问之后,才能删除老对象。读者没有任何同步开销,而写者的同步开销则取决于使用的写者间同步机制。

RCU 适用于需要频繁的读取数据,而相应修改数据并不多的情景,例如在文件系统中,经常需要查找定位目录,而对目录的修改相对来说并不多,这就是 RCU 发挥作用的最佳场景。

RCU 例子

RCU 常用的接口如下图所示:

为了更好的理解,在剖析 RCU 之前先看一个例子:

#include<linux/kernel.h>#include<linux/mole.h>#include<linux/init.h>#include<linux/slab.h>#include<linux/spinlock.h>#include<linux/rcupdate.h>#include<linux/kthread.h>#include<linux/delay.h>structfoo{inta;structrcu_headrcu;};staticstructfoo*g_ptr;staticintmyrcu_reader_thread1(void*data)//读者线程1{structfoo*p1=NULL;while(1){if(kthread_should_stop())break;msleep(20);rcu_read_lock();mdelay(200);p1=rcu_dereference(g_ptr);if(p1)printk("%s: read a=%d\n",__func__,p1->a);rcu_read_unlock();}return0;}staticintmyrcu_reader_thread2(void*data)//读者线程2{structfoo*p2=NULL;while(1){if(kthread_should_stop())break;msleep(30);rcu_read_lock();mdelay(100);p2=rcu_dereference(g_ptr);if(p2)printk("%s: read a=%d\n",__func__,p2->a);rcu_read_unlock();}return0;}staticvoidmyrcu_del(structrcu_head*rh)//回收处理操作{structfoo*p=container_of(rh,structfoo,rcu);printk("%s: a=%d\n",__func__,p->a);kfree(p);}staticintmyrcu_writer_thread(void*p)//写者线程{structfoo*old;structfoo*new_ptr;intvalue=(unsignedlong)p;while(1){if(kthread_should_stop())break;msleep(250);new_ptr=kmalloc(sizeof(structfoo),GFP_KERNEL);old=g_ptr;*new_ptr=*old;new_ptr->a=value;rcu_assign_pointer(g_ptr,new_ptr);call_rcu(&old->rcu,myrcu_del);printk("%s: write to new %d\n",__func__,value);value++;}return0;}staticstructtask_struct*reader_thread1;staticstructtask_struct*reader_thread2;staticstructtask_struct*writer_thread;staticint__initmy_test_init(void){intvalue=5;printk("figo: my mole init\n");g_ptr=kzalloc(sizeof(structfoo),GFP_KERNEL);reader_thread1=kthread_run(myrcu_reader_thread1,NULL,"rcu_reader1");reader_thread2=kthread_run(myrcu_reader_thread2,NULL,"rcu_reader2");writer_thread=kthread_run(myrcu_writer_thread,(void*)(unsignedlong)value,"rcu_writer");return0;}staticvoid__exitmy_test_exit(void){printk("goodbye\n");kthread_stop(reader_thread1);kthread_stop(reader_thread2);kthread_stop(writer_thread);if(g_ptr)kfree(g_ptr);}MODULE_LICENSE("GPL");mole_init(my_test_init);mole_exit(my_test_exit);

执行结果是:

myrcu_reader_thread2:reada=0myrcu_reader_thread1:reada=0myrcu_reader_thread2:reada=0myrcu_writer_thread:writetonew5myrcu_reader_thread2:reada=5myrcu_reader_thread1:reada=5myrcu_del:a=0

RCU 原理

可以用下面一张图来总结,当写线程 myrcu_writer_thread 写完后,会更新到另外两个读线程 myrcu_reader_thread1 和 myrcu_reader_thread2。读线程像是订阅者,一旦写线程对临界区有更新,写线程就像发布者一样通知到订阅者那里,如下图所示。

写者在拷贝副本修改后进行 update 时,首先把旧的临界资源数据移除(Removal);然后把旧的数据进行回收(Reclamation)。结合 API 实现就是,首先使用 rcu_assign_pointer 来移除旧的指针指向,指向更新后的临界资源;然后使用 synchronize_rcu 或 call_rcu 来启动 Reclaimer,对旧的临界资源进行回收(其中 synchronize_rcu 表示同步等待回收,call_rcu 表示异步回收)。

为了确保没有读者正在访问要回收的临界资源,Reclaimer 需要等待所有的读者退出临界区,这个等待的时间叫做宽限期(Grace Period)。

Grace Period

中间的黄色部分代表的就是 Grace Period,中文叫做宽限期,从 Removal 到 Reclamation,中间就隔了一个宽限期,只有当宽限期结束后,才会触发回收的工作。宽限期的结束代表着 Reader 都已经退出了临界区,因此回收工作也就是安全的操作了。

宽限期是否结束,与 CPU 的执行状态检测有关,也就是检测静止状态 Quiescent Status。

Quiescent Status

Quiescent Status,用于描述 CPU 的执行状态。当某个 CPU 正在访问 RCU 保护的临界区时,认为是活动的状态,而当它离开了临界区后,则认为它是静止的状态。当所有的 CPU 都至少经历过一次 Quiescent Status 后,宽限期将结束并触发回收工作。

因为 rcu_read_lock 和 rcu_read_unlock 分别是关闭抢占和打开抢占,如下所示:

staticinlinevoid__rcu_read_lock(void){preempt_disable();}

staticinlinevoid__rcu_read_unlock(void){preempt_enable();}

所以发生抢占,就说明不在 rcu_read_lock 和 rcu_read_unlock 之间,即已经完成访问或者还未开始访问。

Linux 同步方式的总结

资料免费领

学习直通车

⑧ linux 线程间共享内核栈吗

首先,我们知道所有线程共享主线程的虚拟地址空间(current->mm指向同一个地址),且都有自己的用户态堆栈(共享父进程的地址空间,再在里面分配自己的独立栈,默认2M)。这是毫无疑问的,但还有一点我没搞明白,内核栈是共享还是独立的?猜测:独立的。理由:要不然内核栈对应的thread_info中的tast_struct没有办法与每个线程对应起来,因为现在已经有多个task_struct了,但保存内核栈的thread_info(其实是thread_union联合体)中只能保存一个task_struct。所以理论上分析,虽然可以共享地址空间,但每个线程还是需要一个单独的内核栈的。看代码:分析创建线程最终肯定会走到内核函数do_fork()中来的,所以从此函数看起。do_fork()->_process()->p_task_struct()fork.c中p_task_struct()的实现:static struct task_struct *p_task_struct(struct task_struct *orig){struct task_struct *tsk;struct thread_info *ti;unsigned long *stackend;int node = tsk_fork_get_node(orig);int err;tsk = alloc_task_struct_node(node);if (!tsk)return NULL;ti = alloc_thread_info_node(tsk, node);/*就是这里,果然分配内核栈了*/if (!ti)goto free_tsk;err = arch_p_task_struct(tsk, orig);/*这里分配task_struct结构*/if (err)goto free_ti;tsk->stack = ti; ...}

热点内容
自动认购源码 发布:2025-05-10 17:59:08 浏览:916
存储行业发展 发布:2025-05-10 17:56:59 浏览:642
非易失存储技术 发布:2025-05-10 17:47:17 浏览:705
androidstudiosvn 发布:2025-05-10 17:47:14 浏览:112
php下载csv 发布:2025-05-10 17:46:30 浏览:407
为什么每次登录淘宝都要输密码 发布:2025-05-10 17:44:57 浏览:574
战舰世界国服脚本不能用 发布:2025-05-10 17:44:46 浏览:747
asp判断数据库 发布:2025-05-10 17:41:03 浏览:284
刮痘痘解压 发布:2025-05-10 17:35:34 浏览:22
android退出动画 发布:2025-05-10 17:34:02 浏览:512