找到目录 os 的文章 9 篇.


内核内存映射示例

Posted on 2016-04-16 06:19:41 os

摘要:

前一篇博客中,我们介绍了Linux内核中内存映射的实现,以及用户态调用mmap系统调用的原理。接下来,我们会通过Linux内核模块的方式,映射两种物理内存:物理地址空间和内核模块申请的内存,演示内核模块中如何与用户态进程之间共享内存。

在开始之前,介绍一下/dev/mem这个字符设备,打开/dev/mem之后,通过mmap系统调用,就可以映射物理地址空间。需要注意的是,这里的物理地址空间,是站在CPU角度看到的地址空间,而不仅仅是RAM空间,还有外设的IO空间,例如BIOS ROM、Video ROM、PCI BUS等。

下面的内核模块代码工作的行为类似于/dev/mem,打开对应的设备文件后,同样可以映射CPU地址空间。完整的代码在文章最后。

阅读全文

mmap实例及原理分析

Posted on 2015-12-01 11:30:27 os

摘要:

在unix/linux平台下读写文件,一般有两种方式。第一种是首先open文件,接着使用read系统调用读取文件的全部或一部分。于是内核将文件的内容从磁盘上读取到内核页高速缓冲,再从内核高速缓冲读取到用户进程的地址空间。这么做需要在内核和用户空间之间做四次数据拷贝。而且当多个进程同时读取一个文件时,则每一个进程在自己的地址空间都有这个文件的副本,这样也造成了物理内存的浪费。如下图所示:

传统方式读写文件

第二种方式是使用内存映射的方式。首先open文件,接着调用mmap系统调用,将文件的内容的全部或一部分直接映射到进程的地址空间,映射完成后,进程可以像访问普通内存一样做memcpy等操作,不必再调用read/write等操作。mmap并不分配物理地址空间,它只是会占用进程的虚拟内存空间。而第一种方式则需要进程预先分配好物理内存,内核才能将页高速缓冲中的文件数据拷贝到用户进程指定的内存空间中。

使用第二种方式,当多个进程同时访问读取一个文件时,每个进程都将文件内容在内核中的页高速缓冲映射到自己的地址空间。当第一个进程访问内核中的页缓冲时,进程的机器指令会触发一个缺页中断。内核将文件的这一页数据读入到页高速缓冲,并更新进程的页表,使页表指向内核缓冲中的这个页。之后有其他进程再次访问同一页时,该页已经在内存中,内核只需要将进程的页表登记并指向内核中的页高速缓冲即可。如下图所示:

mmap方式读写文件

阅读全文

关于多核编程的一点想法

Posted on 2015-06-30 17:18:28 os

Nim语言有很多语言上先进的特性和接近Python的语法,Rust定位成C++的直接竞争者。 但是请认真思考:这两个语言从一出生开始,都没有解决,而且以后也很难解决本世纪软件业的一次重大危机:多核编程危机。 它们的出现就不是冲着解决多核编程问题来的,基因决定了,靠这两门语言解决不了多核编程的问题。

怎么解决多核编程的问题?

屏蔽硬件上的复杂特性,例如缓存、一致性、内存屏障、原子操作,给程序员简单的并发特性,在编程时存在尽量少的心智负担。

GO可以在内存中创建成千上万的协程,并且提供了协程间通信的基础设施,单凭这两点,Nim和Rust都没有做到。

Nim语言目前没有实现自身的汇编器和链接器,Nim代码首先被编译为C代码,再把C代码编译为本地机器码。 Golang自带汇编器和链接器。

Rust官方最初的目标是像Erlang一样可以创建大量的协程,但是这个目标被官方抛弃了,所以Rust里面是并发执行体不是协程,是OS级别的线程。在高并发场景下,1000个OS的线程同时运行效率就变得非常差。或者可以选择异步模型,但是又面临回调地狱,并且要小心同步IO和CPU密集型计算阻塞当前线程。如果使用第三库必须经过改造以适合异步模型。

因为Rust官方明白,实现完整高效的的协程调度,难度很大。这方面Go做的很好,其他静态编译类型的语言都没有超过它。

我们可以说Nim和Rust的定位不同,要解决各自的目标问题。 但是很多人拿Nim和Go对比的时候,根本没有,而且也不敢把这两种语言的特性和Go的核心特性来对比。

多核编程,是目前遇到的问题,而且是难以解决的问题,谁能解决的高效和优雅,谁就能在未来获胜。

许世伟说过,他在C++中实现协程和协程调度,到头来也只是对Golang的拙劣的模仿,我想Rust官方最初的想法也大概如此吧。

而且我相信Ken Thompson和Russ Rox这两位大师的眼界。

知乎上关于Rust高并发框架实现的问题:http://www.zhihu.com/question/30325880


两张图看懂GDT、GDTR、LDT、LDTR的关系

Posted on 2014-07-26 09:24:01 os

段选择符

32位汇编中16位段寄存器(CS、DS、ES、SS、FS、GS)中不再存放段基址,而 是段描述符在段描述符表中的索引值,D3-D15位是索引值,D0-D1位是优先级(RPL)用于特权检查,D2位是描述符表引用指示位TI,TI=0指 示从全局描述表GDT中读取描述符,TI=1指示从局部描述符中LDT中读取描述符。这些信息总称段选择符(段选择子).

段描述符

8个 字节64位,每一个段都有一个对应的描述符。根据描述符描述符所描述的对象不同,描述符可分为三类:储存段描述符,系统段描述符,门描述符(控制描述 符)。在描述符中定义了段的基址,限长和访问内型等属性。其中基址给出该段的基础地址,用于形成线性地址;限长说明该段的长度,用于存储空间保护;段属性 说明该段的访问权限、该段当前在内存中的存在性,以及该段所在的特权级。

段描述符表:

IA-32处理器把所有段描述符按顺序组织成线性表 放在内存中,称为段描述符表。分为三类:全局描述符表GDT,局部描述符表LDT和中断描述符表IDT。GDT和IDT在整个系统中只有一张,而每个任务 都有自己私有的一张局部描述符表LDT,用于记录本任务中涉及的各个代码段、数据段和堆栈段以及本任务的使用的门描述符。GDT包含系统使用的代码段、数 据段、堆栈段和特殊数据段描述符,以及所有任务局部描述符表LDT的描述符。

GDTR全局描述符寄存器

48位,高32位存放GDT基址,低16为存放GDT限长。

LDTR局部描述符寄存器

16位,高13为存放LDT在GET中的索引值。

IA-32处理器仍然使用xxxx:yyyyyyyy(段选择器:偏移量)逻辑方式表示一个线性地址,那么是怎么得到段的基址呢?在上面说明中我们知道,要得到段的基址首先通过段选择符xxxx中TI位指定的段描述符所在位置: 当 TI=0时表示段描述符在GDT中,如下图所示:① 先从GDTR寄存器中获得GDT基址。② 然后再GDT中以段选择符高13位位置索引值得到段描述符。③ 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。

当TI=1时表示段描述符在LDT中,如下图所示:① 还是先从GDTR寄存器中获得GDT基址。② 从LDTR寄存器中获取LDT所在段的位置索引(LDTR高13位)。③ 以这个位置索引在GDT中得到LDT段描述符从而得到LDT段基址。④ 用段选择符高13位位置索引值从LDT段中得到段描述符。⑤ 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。


linux下的自动编译工具

Posted on 2014-01-23 13:43:54 os

在Linux下面,编写makefile是一件辛苦的事情。因此,为了减轻程序员编写makefile的负担,人们发明了autoconf和automake这两个工具,可以很好地帮我们解决这个问题。 我们可以通过一个简单的示例来说明如何使用配置工具。

1. 首先,编写源文件hello.c。
#include <stdio.h>

int main(int argc, char** argv[])
{
    printf("hello, world!\n");
    return 1;
}
2. 接下来,我们需要创建一个Makefile.am,同时编写上脚本。
SUBDIRS=

bin_PROGRAMS=hello
hello_SOURCES=hello.c  
3. 直接输入autoscan,生成文件configure.scan,再改名为configure.in。

修改脚本AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)

为AC_INIT(hello, 1.0, roy@rootk.com)

同时,在AC_CONFIG_HEADER([config.h])后面添加

AM_INIT_AUTOMAKE(hello, 0.1)

4. 依次输入aclocal命令、autoheader命令
5. 创建4个文件,分别为README、NEWS、AUTHORS和ChangeLog
6. 依次输入automake -a、autoconf命令
7. 输入./configure,生成最终的Makefile
8. 如果需要编译,输入make;如果需要安装, 输入make install;如果需要发布软件包,输入make dist

更改linux栈空间大小

Posted on 2013-07-31 09:49:34 os

  1. 通过命令 ulimit -s 查看linux的默认栈空间大小,默认情况下 为10240 即10M

  2. 通过命令 ulimit -s 设置大小值 临时改变栈空间大小:ulimit -s 102400, 即修改为100M

  3. 可以在/etc/rc.local 内 加入 ulimit -s 102400 则可以开机就设置栈空间大小

  4. 在/etc/security/limits.conf 中也可以改变栈空间大小:

 #<domain>      <type>  <item>         <value>

     *           soft    stack      102400

重新登录,执行ulimit -s 即可看到改为102400 即100M


Nginx对后端upstream server启用keepalive

Posted on 2013-03-06 19:49:00 os

Nginx upstream目前只有短连接,通过HTTP/1.0向后端发起连接,并把请求的”Connection” header设为”close”。Nginx与前端的连接默认为长连接,一个用户跟Nginx建立连接之后,通过这个长连接发送多个请求。如果Nginx只是作为reverse proxy的话,可能一个用户连接就需要多个向后端的短连接。如果后端的服务器(源站或是缓存服务器)处理并发连接能力不强的话(比如单进程的squid),就可能导致瓶颈的出现。

从nginx 1.1.4 开始有了原生的ngx_http_upstream_keepalive 模块 和”proxy_http_version” fastcgi_keep_conn” 等指令 ,所以后端的keepalive也在nginx 1.2.0成为现实。

HTTP 1.0实现长连接需要设置请求头部Connection: keep-alive。 但是在HTTP 1.1 中已经不需要了。 所以在nginx中这样配置:

upstream http_backend {
    server 127.0.0.1:8080;
 
    keepalive 16;
}
 
server {
    ...
 
    location /http/ {
        proxy_pass http://http_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        ...
    }
}

常用iptables设置

Posted on 2013-01-18 13:28:48 os

#!/bin/bash

# Name of wan and lan interface
wan_interface=eth1
lan_interface=eth0
vbox_int=vboxnet0

# Where is iptables
BIN=/sbin/iptables

$BIN -X
$BIN -F
$BIN -F -t nat 
$BIN -F -t raw

#$BIN -P INPUT DROP
$BIN -P INPUT ACCEPT
$BIN -P OUTPUT ACCEPT
$BIN -P FORWARD ACCEPT

$BIN -A INPUT  -p icmp --icmp-type any -j ACCEPT
$BIN -A INPUT  -i lo -j ACCEPT
$BIN -A INPUT  -m state --state RELATED,ESTABLISHED -j ACCEPT
$BIN -A INPUT -i $vbox_int -j ACCEPT
$BIN -A INPUT  -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
$BIN -A INPUT  -m state --state NEW -m tcp -p tcp --dport 10000 -j ACCEPT
$BIN -A INPUT  -m state --state NEW -m tcp -p tcp --dport 10001 -j ACCEPT
#$BIN -A INPUT  -m state --state NEW -m tcp -p tcp --dport 55555 -j ACCEPT
#$BIN -A INPUT  -m state --state NEW -m tcp -p tcp --dport 631 -s 192.168.56.0/24 -j ACCEPT
#$BIN -A INPUT  -m state --state NEW -m tcp -p tcp --dport 5672 -j ACCEPT
$BIN -A INPUT  -j REJECT --reject-with icmp-host-prohibited

$BIN -t nat -A POSTROUTING -s 192.168.56.0/24 -o $wan_interface -j MASQUERADE
$BIN -t nat -A POSTROUTING -s 192.168.56.0/24 -o $lan_interface -j MASQUERADE


#$BIN -t nat -A PREROUTING -s 172.16.10.0/24 -i eth1 -p tcp --dport 80 -j REDIRECT --to-port 3128
#$BIN -t nat -A POSTROUTING -s 172.16.9.0/24 -o $wan_interface -j SNAT --to $wan_ip
#$BIN -t raw -A PREROUTING -s 172.16.10.0/24 -j ACCEPT
#$BIN -t raw -A PREROUTING -s 172.16.0.0/16 -m string --algo bm --string "youku.com" -j DROP
#$BIN -t raw -A PREROUTING -s 172.16.0.0/16 -m string --algo bm --string "ku6.com" -j DROP
#$BIN -t raw -A PREROUTING -s 172.16.0.0/16 -m string --algo bm --string "6.cn" -j DROP

linux多进程和libnotify桌面通知

Posted on 2012-12-27 15:15:04 os

OS: ubuntu 12.04 x86_64

GCC: gcc 版本 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <libnotify/notify.h>

void usage(char *);
int close_std();

char * const short_options = "hf:b:";

struct option long_options[] =
{
    {"help", 2, NULL, 'h'},
    {"file", 2, NULL, 'f'},
    {"body", 2, NULL, 'b'},
    {NULL, 0, NULL, 0},
};

char * player = "/usr/bin/mplayer";
char * music_file = "/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga";
char * notify_body = "You have a new mail!";

void usage(char * program_name)
{
    printf("%s -h [--help]\n"
           "-f [--file=filename] path to music file.\n"
           "-b [--body=content] the body of notify dialog.\n", program_name);
    exit(1);
}

int close_std()
{
    if(close(STDIN_FILENO) == -1 || close(STDOUT_FILENO) ==-1 || close(STDERR_FILENO) == -1)
    {
        return -1;
    }

    return 0;
}

int main(int argc, char **argv)
{

    int c, pid;
    char * program_name = argv[0];

    while((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1)
    {
        switch(c)
        {
            case 'h':
                usage(program_name);

            case 'f':
                music_file = optarg;
                break;

            case 'b':
                notify_body = optarg;
                break;

            case '?':
                usage(program_name);

            default:
                usage(program_name);
        }
    }

    if((close_std() == -1))
    {
        perror("close_std()");
        exit(1);
    }

    //player music
    if((pid = fork()) < 0)
    {
        perror("fork()");
        exit(1);
    }
    else if(pid == 0)
    {
        if(execlp(player, player, music_file, (char *)0) < 0)
        {
            perror("execlp()");
            exit(1);
        }

        exit(0);
    }

    //send notify information to user
    if((pid = fork()) < 0)
    {
        perror("fork()");
    }
    else if(pid == 0)
    {
        notify_init("Mail");
        NotifyNotification * notify = notify_notification_new("Mail", notify_body, "Mail");
        notify_notification_show(notify, NULL);

        exit(0);
    }

    exit(0);
}

Makefile:

SRC_INCLUDE=-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/gdk-pixbuf-2.0

CC=gcc
MOD_CFLAGS=-fPIC
CFLAGS=-g -O2 -DHAVE_CONFIG_H -DNSCORE
LDFLAGS=
LIBS=-lnotify 
DBG_FLAGS= -DDEBUG=1

OUT_PROGRAM= newmail_notify


all: main

main:
    $(CC) $(MOD_CFLAGS) $(CFLAGS) $(SRC_INCLUDE) -o $(OUT_PROGRAM) newmail_notify.c $(MOD_LDFLAGS) $(LDFLAGS) $(LIBS)