Aug 14

启用BitLocker 不指定

felix021 @ 2016-8-14 15:11 [IT » 操作系统] 评论(1) , 引用(0) , 阅读(9062) | Via 本站原创
  记得很早之前就想给自己的电脑启用BitLocker,但是因为主板上没有TPM芯片(用于存储密钥),不能给系统盘加密;而不能给系统盘加密的话,其他盘的解密就需要每次启动系统以后再输入一次密钥,然而我的文档、桌面等一般都存在其他盘,我估摸着会出现一些奇怪的现象,所以就放弃了。

  昨天兴起又研究了一下,发现其实并不是一定要有TPM才能给系统盘加密——这个限制是Vista第一次引入BitLocker时的要求,后来微软也意识到,由于大多数民用主板上没有集成TPM芯片,导致BitLocker略显鸡肋,于是在Win7开始做了个变通,允许把加密密钥保存在单独的启动分区里,通过一个密码(或启动U盘)来保证密钥的安全,但是需要在组策略编辑器里将“计算机配置-管理模板-Windows组件-BitLocker驱动器加密-操作系统驱动器-启动时需要附加身份验证”修改为已启用(注意左下角“没有兼容的TPM时允许BitLocker”被打上勾了),系统盘被加密后,用户在启动电脑时输入一次密码即可。

  对Win7系统安装略有了解的同学大概注意到了,从光盘启动安装的话,除了Windows系统盘之外,还会创建一个100M的启动分区和一个恢复分区,其中启动分区一方面是为了兼容UEFI,另一方面也一定程度上解决了TPM的问题。虽然可以不需要TPM了,但是必须要说一句,这样的安全性还是降低了,因为启动分区是不加密的,这意味着有心人可以替换启动器(启动分区的第一个扇区,或者BOOTMGR)收集到用户启动时输入的密码,从而获得加密密钥。我想这也是微软默认仍然需要tpm,除非用户主动修改组策略的原因吧。

  我以前一直很不喜欢Windows的这个小动作(凭什么一个系统要占据3个Primary Partition?要知道MBR方式的磁盘只支持4个主分区或者3主分区+1逻辑分区,这么搞真浪费),所以我通常是用Wim安装器来安装,只需要一个主分区,结果就把自己坑了……还好补救措施很简单,通过DiskPark或者DiskGenius或者diskmgmt.msc开一个100M的主分区(注意不能是逻辑分区),激活(设置为启动分区,此处应有55AA梗),格式化为NTFS,把系统盘的 boot目录、bootmgr文件 拷贝到新分区,再用管理员cmd执行 "bcdedit /export X:\boot\bcd" ,重启电脑后就能开启BitLocker了。

  开启了BitLocker以后感觉心里踏实了很多,毕竟Chrome保存了那么多密码,如果电脑被偷走还是挺头疼的。
Apr 23

Linux的文件锁机制初探 不指定

felix021 @ 2016-4-23 17:10 [IT » 操作系统] 评论(1) , 引用(0) , 阅读(9001) | Via 本站原创
# Linux下的文件锁

Linux下有两种不同的文件锁机制,一种是通过 `flock` 系统调用锁定整个文件,另一种是通过 `fcntl` 系统调用完成更细粒度的记录锁(锁定文件中的一个区间)

## open 系统调用

Linux下,在最初打开一个文件的时候,最终都是落到 `open` 这个系统调用上。

open系统调用 (@./fs/open.c) 里头做了很多事情,跟文件锁有关的主要是分配了一个 `struct file` 对象,并且给该对象的 `struct inode *inode` 和 `struct file_operations *f_op` 成员赋值。

其中 inode 是对应文件系统分配的(比如 ext4 文件系统的分配函数是 fs/ext4/inode.c 中的 `ext4_iget`),其中也包含了一个 `struct file_operations *` 类型的成员 `i_fop`,即针对该文件系统文件操作的函数指针集合。

以上提到的两次 `struct file_operations` 对象包含 `flock` 和 `lock` 两个函数指针,分别用于上述的两种不同锁机制。

内核在内存中只会给一个文件分配一个 `struct inode` 对象,不同进程打开同一个文件时,都会单独分配一个 `struct file` 对象,且 `file->inode` 指向该文件的 inode 对象。其中 `file->f_op` 实际上就是在 open 系统调用过程中,从 `inode->i_fop` 拷贝过来的。(open -> do_sys_open -> do_filp_open -> path_openat -> do_last -> vfs_open -> do_dentry_open: `f->f_op = fops_get(inode->i_fop)`)

## flock 加锁

内核首先根据第一个参数 fd 找到内核中的 `struct file` 对象,判断 `file->f_op->flock` 指针是否有效,如果有效(说明是文件系统指定了加锁机制,例如 fs/fuse/file.c 中指定了 `fuse_file_flock` 函数,而ext2/3/4都没有指定),就调用该指针指向的函数来执行加锁操作;如果不存在,则执行`flock_lock_file_wait` -> `flock_lock_inode` (@ fs/locks.c)。

如果文件系统没有指定加锁机制,那么 `flock_lock_inode` 会尝试给该文件对应的 inode 加文件锁(加锁机制比较琐碎,我没看得太细,大致是获取 `inode->i_flctx->flc_lock` 这个spinlock,然后增删改查 `inode->i_flctx->flc_flock` 这个链表,但是可以看出全是内存操作)。

如果文件系统指定了加锁机制,那么就要具体考察对应的加锁机制是否是持久化在磁盘上了;不过从加锁这个需求本身去考察,大多数情况下应当是期望能够快速完成,除了特殊情况,可以认为只是在内存中操作,并没有持久化到磁盘。

## fcntl 加锁

与 `flock` 类似,区别在于判断的函数指针是 `file->f_op->lock` ,如果不存在的话,则调用 `posix_lock_file` 尝试对该 inode 加记录锁,也是在 `inode->i_flctx` 上面倒腾,不过用的是 `inode->i_flctx->flc_lock` 这个链表,具体操作更复杂一些,还要考虑记录锁区间的交叉等问题。

## 结论

具体情况取决于文件系统是否使用了指定的加锁机制,如果有,要单独分析。

如果没有,所有的锁都是在内存中维护的,如果断电,锁自动失效。这样也比较符合通常的理解。

另外,针对文件的加锁行为一般都是进程执行的,进程退出(包括意外退出)时,exit 系统调用会调用 `exit_files` 函数清理该进程打开的所有文件。
Dec 25
== 卸载OneDrive ==

C:\windows\syswow64\onedrivesetup.exe /uninstall



== 卸载 Cortana ==



== 禁用Win+S ==

下载文件 (已下载 376 次)


== 从“此电脑”删除垃圾链接(图片、视频等) ==



== 禁用锁屏界面 ==

组策略-》计算机配置-》管理模版-》控制面板-》个性化-》不显示锁屏

p.s. 周年更新以后这个方法失效了,得这么用:

本地安全策略 -> 软件限制策略 -> 新建路径规则, 路径填写“C:\Windows\SystemApps\Microsoft.LockApp_cw5n1h2txyewy”,安全级别“不允许”

== 禁止自动更新 ==

不让我设置禁止自动重启,那只好禁止自动更新了。

services.msc -> windows update -> 禁止

== windows图片查看器 ==

下载文件 (已下载 380 次)


== 图片查看器背景色发黄 ==

显示设置->高级显示设置->显示适配器属性->颜色管理

设备:选择显示器,勾上“使用我对此设备的设置”,“添加(A)...",选择 “sRGB IEC61966-2.1”,"设置为默认配置"

== 垃圾APP ==

有些右键就卸载掉了(但实际上文件还在),这样删比较干净点。

打开Powershell执行:

Get-AppxPackage -name Microsoft.ZuneMusic | remove-appxpackage
Get-AppxPackage -name Microsoft.XboxApp | remove-appxpackage
Get-AppxPackage -name Microsoft.WindowsMaps | remove-appxpackage
Get-AppxPackage -name Microsoft.BingWeather | remove-appxpackage
Get-AppxPackage -name Microsoft.ZuneVideo | remove-appxpackage
Get-AppxPackage -name Microsoft.BingSports | remove-appxpackage
Get-AppxPackage -name Microsoft.3DBuilder | remove-appxpackage


p.s. 打包下载
下载文件 (已下载 375 次)

Aug 18

T02安装Ubuntu 不指定

felix021 @ 2015-8-18 12:42 [IT » 操作系统] 评论(1) , 引用(0) , 阅读(8829) | Via 本站原创
前一段时间在京东众筹购入了一款名为"光棍一号T02"的电视棒,Intel Atom Z3735F(1.33G*4) + 2GB + 32GB,之所以下单,最主要是因为他们家宣称可以支持ubuntu(因为看起来好像就是intel Compute Stick的贴牌产品,而后者是可以支持ubuntu的)。可是到手了以后,问了众筹方才知道,原来安装镜像是不公开的,需要在发货前说明,他们装好再寄过来(跟Compute Stick一个尿性,据说是因为第三方的驱动问题)。没办法,只能自己动手了。

在网上有找到一篇文章:Install Ubuntu 14.04 LTS on the 2GB Intel Compute Stick,里面提供了一个64bit的ubuntu 14.04镜像以及相应的步骤,可以直接安装到2GB内存的Compute Stick上;以及对安装32位UEFI支持的一些说明和提示。

情况是这样的,Intel Compute Stick 2GB+32GB版本出厂预装的是Win8,Ubuntu版本只有1GB内存+16GB存储,所以大神们琢磨着弄一个ubuntu到windows版本上去,可是intel官方因为第三方驱动的原因不开放镜像,于是他们自己做了一个。从文章里可以知道,stick可以在UEFI里面选择启动的系统是32bit的win8还是64bit的ubuntu,也就是说可以按需向操作系统提供32/64位的uefi接口。可是手头的这个T02只有一个非常简单的32bit的uefi,所以整个过程就比较蛋疼了,下面记录一下,备忘。

1. 准备相关材料:http://pan.baidu.com/s/1qWN0jIO

包括 (a) 安装镜像 ubuntu14.04_z3735f.iso (b) 启动盘制作工具rufus-2.2p.exe (c) 支持32位efi的grub启动器 bootia32.efi (d) 安装后用的grub.tar.gz

2. 制作启动盘

使用rufus把iso写入到一个U盘里。把wubi.exe删掉,然后把 bootia32.efi 放到 EFI/BOOT/ 下面

3. 安装使用启动盘启动T02,可以先通过"try ubuntu"体验一下效果。

(a) 如果希望直接安装的话,建议选择"install ubuntu",比较快且不容易出错。
(b) 安装中进行分区的时候选择“使用整个磁盘”,这样会自动使用GPT分区并且创建一个EFI分区。由于本身内存空间有限,建议不要考虑双系统共存了。
(c) 安装期间建议不要插入TF卡,速度超慢。不要安装到TF卡上。

==》 安装完以后不要急着重启

4. 复制文件

(a) 把启动盘上的 bootia32.efi 拷贝到新系统的 [挂载点]/boot/efi/EFI/ubuntu 下面去。
(b) 把grub.tar.gz的内容解压到新系统的随便一个目录中(比如 /home 下面)

5. 重启,进入EFI Shell(因为新系统中没有包含配置好的32位的启动程序,会自动进入到EFI Shell中)

6. 进入grub shell:根据前面列出文章中的Troubleshooting,按顺序执行:

(a) fs0:  (注意冒号)
(b) cd EFI\ubuntu
(c) bootia32.efi  (于是就引入了一个裸的grub shell)

7. 进入initrd中的准系统:在grub shell下面执行

(a) linux (hd0,gpt2)/vmlinuz
(b) initrd (hd0,gpt2)/initrd.img
(c) boot

于是就进入了initrd中的

8. 安装支持32位efi的grub(这一步可能有点坑)

(a) chroot到rootfs
$ mkdir -p /rootfs
$ mount -o rw -t ext4 /dev/mmcblk0p2 /rootfs
$ mount -t vfat /dev/mmcblk0p1 /rootfs/boot/efi/
$ mount --bind /dev /rootfs/dev
$ cd /rootfs
$ chroot .

(b) 安装grub-ia32
这一步记得不是很清楚了,因为各种deb包有奇怪的依赖关系,总之大概是这样的:
$ cd /home
$ dpkg -i *.deb
根据报错信息按需卸载系统中存在的package(dpkg --remove xxx),然后再重新执行上一句直到安装成功。

【注意】安装成功以后、在/rootfs/efi/EFI/ubuntu下面应该有 grubia32.efi 这个启动器。

(c) 重启进入efi shell
是的,因为efi配置里面还是grubx64.efi

9. 从EFI Shell启动进入系统

> fs0:
> cd EFI\ubuntu
> grubia32.efi

一切顺利的话,这里就可以启动进入ubuntu了(真心不容易啊)。

10. 更新EFI配置(依然是参考文章的Troubleshooting)

$ sudo apt-get install efibootmgr #安装EFI配置编辑器
$ sudo efibootmgr -v #查看当前的efi配置(可以看到启动器是grubx64.efi,这个不对!)
$ sudo efibootmgr -b 0003 -B  #删除所有的EFI启动配置
$ sudo efibootmgr -b 0002 -B
$ sudo efibootmgr -b 0001 -B
$ sudo efibootmgr -b 0000 -B
$ sudo efibootmgr -c -d /dev/mmcblk0 -p 1 -l \\EFI\\ubuntu\\grubia32.efi -L ubuntu #加入新的启动配置
$ sync

大功告成!


==== 后记 ====

启动以后就可以按需安装各种package拉,运行速度还不错,wifi没问题,显卡驱动正常(可以调整分辨率),但是声音和蓝牙驱动貌似还是有问题,不过对于一个server来说都是小问题。稍微麻烦一点的是wifi的延迟不太正常,局域网每两次ping的响应,差不多都会有一次是1000ms, 一次是2ms,但是加上一个有线USB网卡以后就都一切正常(这是什么病啊。。),接下来再试用几天看看拉。

@2015-08-19
T02是Intel Atom Z3735f (BayTrail)方案(x86_64 1.33GHz*4 + 2GB ram + 32GB rom),手头有一个2012年8月份入手的mk802(居然已经3年了,依然在勤恳地工作!感谢它!),全志A10的方案(Cortex A8 1GHz*1 + 1GB ram + 4GB rom),二者都安装上了Ubuntu Desktop版,正好作个对比。

实际使用上二者性能上的差距还是蛮直观的,ssh登录的时候,T02马上就进入shell,MK要卡几秒;在桌面程序上,比如Chrome,启动速度、页面加载速度什么的,感受很明显。下面是一些简单的测试数据:

1. 最简单的,编译一个 busy.c 源码(main里面只有一行 while(1)):
T02:0.124s
MK:0.251s

2. 编译bash
以前自己打过patch的一个bash的tar.gz

2.1 解压
T02:0.598s
MK:1.552s

2.2 configure
T02:1min
MK:1min56s

2.3 make
T02:2min16s(make),45s(make -j4)
MK:5m26s

3. CPU温度/频率@T02
这个只测T02:一个720p视频播放、5个busy.c的a.out,测试是否有风扇对着吹的情况
无风扇:81°C/81°C/87°C/87°C,500MHz*4(自动降频)
有风扇:72°C/72°C/79°C/79°C,1333MHz*4(继续吹可能还会再降点温)
Jul 7

记一个诡异的问题 不指定

felix021 @ 2015-7-7 23:36 [IT » 操作系统] 评论(0) , 引用(0) , 阅读(6579) | Via 本站原创
今天Sandy同学在开发一个网络相关应用的时候遇到了一个奇怪的问题。

大约是这样的一个单例类Foo(以下是类python的伪代码,实际是VB.NET),当调用方法 bar('remove', key, value) 的时候,经常(而不是总是)在for循环过程中报错,错误信息是 "循环过程中_pool已经被修改" 。

class Foo(singleton):
    _pool  = {}
    _mutex = Threading.Mutex()

    def bar(self, action, key, value=None):
        self._mutex.waitOne()

        if action == 'add':
            self._pool[key] = value

        else: #'remove'
            remove_keys = []
            for key, value in self._pool.items():
                if do(key, value):
                    remove_keys.append(key)
            for key in remove_keys:
                del self._pool[key]

        self._mutex.release()

_pool 作为Foo的一个私有成员并且被 _mutex 保护着,理论上是不会出现这个问题的,然而各种排查的表现都指向了线程间的竞争问题,因为只有在调用到 bar('add', key, value) 的时候,_pool才有可能被修改到。

仔细查了一下MSDN上面Threading.Mutex的说明,在备注一栏中藏着一句话:"拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行。",也就是说,如果是同一个线程两次调用 bar 方法的话,这个 _mutex 就相当于失效了。

换用其他的互斥锁机制(例如Syncing)并不解决这个问题(事实上Mutex已经是第三个尝试选项了)。我们甚至试着采用Threading.Senaphore,然而却导致整个进程卡住。

于是debug.print把 Threading.Thread.CurrentThread.ManagedThreadId 输出到控制台,发现在出现错误之前是有多个不同的thread id,但是出错的时候,确实是同一个线程两次调用 bar 方法,也就是说 _mutex 确实不能解决这个问题。

经过各种排查,确认是正好在 do(key, value) 方法调用中出现的,然而 Sandy 同学信誓旦旦地保证,do(key, value) 方法绝对不可能递归地调用回到 bar 函数。由于 do(key, value) 内部调用了某个阻塞的网络请求,据此我推测,.NET的网络模型底层使用了线程+纤程的模型,那只能想办法了。通过查看 bar('add', key, value) 的调用栈,发现确实这是同一个请求,但是中间夹杂了一个未知的"外部过程",也就是说空闲的线程被调度来做其他的事情了。

深坑一个,但是既然找到了原因,就可以考虑如何针对性地去解决它,初步的想法是,Semaphore理论上应该是可以解决这个问题的,可能之前没有细看MSDN、调用方式有点问题。

Sandy同学的想法是,既然 Mutex 已经过滤掉了线程间的冲突,那我们就自己模拟 Semaphore 来解决线程内的冲突,只要简单增加一个初始值为 0 的 _counter 变量 ,在 self._mutex.waitOne() 后面加上
while self._counter != 0:
    Threading.sleep(10) #10ms
self._counter = 1

并在 self._mutex.release() 之前执行 self._counter = 0 就可以了。

想法是美好的,但是一执行就卡死在 foo('add') 调用的 while 循环里。简单分析一下就能发现,这个线程既然一直在while循环里面,就不可能被调度回到 foo('remove') 的纤程去修改 _counter=0,于是就卡住了。

没办法,再回头去仔细看MSDN,Threading.Semaphore 确实没有类似Mutex这样的同线程调用,于是把这个代码按照Example重新写了一遍,但是还是卡死了……

然后我瞬间醒悟过来——这似乎根本就是一个因为.net底层实现导致的死锁!除非上层应用能控制线程调度的细节,否则无论是信号量还是修改过的Mutex(同一个线程不能多次获得的)都不能解决这个问题。于是暂时的结论是可能要采用自己实现的线程池来进行调度,但是改动似乎很大。

完。

UPDATE @ 20150720 后来仔细考虑了下,根本问题是在do(key, value) 内部调用的那个“阻塞”请求,在临界区内本就不该调用阻塞请求。按照.net的文档,那个请求应当是非阻塞的,但是不知道为什么在这里阻塞了。由于我对.net并不了解,我没有再继续追究了。
Jan 22
本系列的上一篇已经是两年多前的事情了……

当时那个版本一直用到今天,已经有点勉强了,主要是基于XP的PE不能识别这两年新出的主板SATA(比如现在这块B85),很蛋疼,得先到BIOS里改成IDE模式再进PE才能操作磁盘。于是今天找了下小聪同学的TonPE,发现也已经物是人非,TonPE已经出售,风格大变,也不太好用了(安装包里的关键文件都用密码加密了,至于吗),哎,后妈就是不一样啊。幸好万能的互联网保存了早先发布的版本,于是找到了基于Win7的PE,Ton7PE_V3.3(基于Win8的Ton8PE_V4.0也能找到,但是在VBox里测试有问题,所以就没用它)。V3.3这个版本做得很赞,不仅能识别SATA,连USB3接口都能识别,而且所有文件都打包到一个WIM里头,不需要像上次那样做一些蛋疼的改动就能用。

装到U盘第二个分区的好处如上一篇所说:1. 跟U盘的正常数据区分开来,不会误删,甚至格式化U盘分区都无所谓;2. PE是单独的分区,而且永远不会被Windows载入,不用担心病毒感染;3. (相比于量产+ISO的U盘)在资源管理器里不会显示多余的分区。

具体的操作流程如下:

1. 用Disk Genius给U盘最后留下90M的空间,格式化成FAT16分区,激活该分区(即设置启动标志)

2. 用Disk Genius把TonPE_V3.3.iso里的内容导入(新版是在邮件菜单中选择“复制文件到当前分区”)到该分区(在分区参数右边有个“浏览文件”的TAB),或者可以在Linux下挂载后拷贝进去

3. 用BOOTICE.exe修改第二个分区的引导记录为“BOOTMGR引导程序”

然后就可以用这个U盘启动啦。

上面涉及到的所有工具/文件都放在这里啦: http://pan.baidu.com/s/1qW4PogW ,包括最原始的安装文件。
Nov 20
UPDATE@2014-01-09:注意,在gdb attach成功以后,这个进程会被暂停,可能会导致一些问题。

UPDATE@2017-11-11:用 lsof | grep deleted ,可以看到被删除的问题文件,记住占用该文件的PID, 到 /proc//fd (该进程打开的所有文件,按fd的值命名)下面可以看到这个文件的另一个链接,把这个链接的内容清空,也可以解决。

场景:Linux下,进程 p 打开了文件 f 并在后台持续地往 f 中写入日志。某日发现磁盘空间不够,把 f 删掉了,这时 ls 已经看不到 f 的存在,但是 df 发现磁盘空间占用并未减小,而 lsof 仍然可以看到该文件被占用(并显示一个 "(deleted)" 后缀)。更不幸的是,进程 p 是需要长期驻留在后台运行的,不能直接干掉它。

解决:简单地说就是替进程解决这个文件。

1) 根据链接文件名,可以查到该文件在进程中的 fd :

    $ ls -l /proc/[PID]/fd

2) 通过 gdb 连上该进程(一般需要root权限)

    $ sudo gdb
    (gdb) attach [PID]

3) 清除该文件所占空间

    (gdb) call ftruncate(3, 0)    #这里假定fd = 3
    $1 = 0

这种方式治标,但不治本 —— 随着进程的继续运行,被删掉的 f 仍然会占用越来越多的空间;但是又不能残暴地直接 close(3) ,否则 p 的后续写入操作会出错,可能导致进程报错结束。

但是还是有办法的:

4) 移花接木

    (gdb) call dup(3)
    $2 = 4
    (gdb) call open("/dev/null", 2)    #注:2 = O_RDWR,x86/x86_64/arm上都是这个值。
    $3 = 5
    (gdb) call dup2(5, 3)
    $4 = 3
    (gdb) call close(4)
    $5 = 0

注:未测试该招式是否可能因多线程竞争导致错误。

OVER.
Nov 9

气球 不指定

felix021 @ 2012-11-9 16:25 [IT » 操作系统] 评论(0) , 引用(0) , 阅读(3484) | Via 本站原创
当主机内存想要回收一些虚拟机的内存时,应该怎么办?根据主机OS记录的内存使用情况来swap虚拟机占用的内存吗?不是的。

因为只有虚拟机的OS知道哪些内存是它最需要的,所以让它自己释放内存才是最好的选择。

怎么让它自己释放内存呢?对虚拟机比较了解的同学应该知道气球驱动(Balloon Driver)。其实原理很简单,就是在虚拟机里,使用一个进程申请并占用*一批内存,于是虚拟机的OS便会把它最不需要的内存释放掉一部分。然后这个进程再告诉主机OS我拿到这么多空间啦,你拿去用吧。

*注:申请内存和占用内存通常是两回事。实际上每次调用malloc(其实关键是brk系统调用)等函数分配内存时,系统只是在页表中标记,某些页已经分配给进程XXX,但是对应的地址空间并不是可用的。当进程使用到这个页的时候,会产生一个缺页中断,在中断例程中,系统才会真的从可用空间中分配一个页,然后再让进程继续使用。这个过程对于进程而言是透明的(不过可能会有一些延迟)。这种延迟机制在OS中大量使用,被实践证明是相当有效的,所以有拖延症的同学们,不要太担心哟,适当的拖延不见得是坏事

说了这么多废话,其实没到这篇的主题。

最近觉得公司配的台式机内存不够了(4G),由于我经常开大量的程序和网页,直到内存不够用的时候才关(拖延。。),因此常常是开个虚拟机的时候空闲内存立刻下降到0,这时候各种卡顿,连鼠标都卡。于是自费在X东上买了一条南亚易胜的 4GB DDR3 1600(这个牌子最便宜。。据说这个牌子给许多其他品牌代工)。安装上去以后,内存不够的现象显著缓解,一口气开三个虚拟机,腰不酸腿不痛的。

但是才用了两天,又发现空闲内存*不多了,经常是才剩下几百MB,有时竟然只有几十MB。虽然任务管理器里说了,8G内存中有6G+是“已缓存”,但是还是怀疑是不是某个进程占用了太多内存。于是一个一个截,全都截掉,空闲内存也还是不到2GB。

*注:Win7任务管理器中的”空闲内存“是malloc以来马上就可以给分配的。“已缓存”是操作系统用内存来保存硬盘上的数据(因为硬盘太慢了)。“可用内存”包括了空闲内存和已缓存。当进程申请的内存超过空闲内存的大小后,OS可以将已缓存的一部分释放掉,但是这个过程需要占用一段时间,效率不如直接使用空闲内存。

于是写了个气球程序来测试。最初的程序很简单,就是while(1)死循环,申请4M内存,然后memset清零。编译,运行,梆!出错了。看了下,进程只申请了2G的内存就出错了,简单分析就知道了,因为我用的是mingw32,生成的32位程序只能申请最多2G的用户空间,再一次malloc的时候,返回的是NULL,于是memset就会导致非法内存访问。

解决办法很简单…………跑3个,然后就申请并占用了6G内存,结束掉之后,发现任务管理器中的已缓存只剩一点点,空闲内存有了6G+,效果显著。看来的确是都被缓存了,不是某个进程太坑爹。注意:如果多跑了一个,就会发现第四个进程速度显著变慢,甚至拖累系统变得相当相当慢,说明这个时候已经在使用swap(或者叫交换空间,虚拟内存,pagefile,页面文件,分页文件……)了,所以不得不慢。

当然,更好的解决方法是编译生成一个64位的可执行程序,这样就能够申请足够多的空间了。到mingw64去下载一个rubenvb编译好的gcc套装直接编译,或者安装个visual studio,都行。

实际上,让系统放弃所有的缓存并不是好事。在XP以及更早的windows系统中,大概是开发人员假设用户的内存总是不够用的,所以系统不会大量使用内存来进行缓存。所以在内存大降价而vista/win7还没出来的时候(大概是08~09年),在XP机器上看到1~2G甚至更多内存并不奇怪,用不完怎么办呢?RAMDISK大行其道,虚拟一个分区出来,速度快得不行。XP的继任者Vista和Win7效仿Unix/Linux系的做法,尽量将内存用于缓存,在很多情况下都可以通过避免磁盘IO来提高系统的反应速度。

因此这里给出个最终版本,默认只回收2G的缓存,再怎么样,缓存3~4G的数据应该已经足够应付日常应用了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const int block = 1024 * 1024 * 8; //8M

int main(int argc, char *argv[])
{
    int i = 0, reclaim = 2048;
    if (argc > 1)
        sscanf(argv[1], "%d", &reclaim);

    for (i = 0; i < reclaim; i += 8)
    {
        char *c = (char *)malloc(block);
        if (c == NULL)
            return 0;
        memset(c, 0, block);
        printf("%4d MB\n", i);
    }
    return 0;
}
分页: 2/18 第一页 上页 1 2 3 4 5 6 7 8 9 10 下页 最后页 [ 显示模式: 摘要 | 列表 ]