2010年7月28日

上周导师布置了一个小程序,让我们熟悉unix下共享内存的编程方法,题目简要描述如下:

      设计一个C/S结构的程序,在开辟的一片共享内存中读写程序,server端负责写数据(每次写一个长度为100的数组),client端负责读数据,并把100个数组的元素以tab符号分割的输出到一个文件中。在不使用任何锁和信号量的情况下完成数据读写的同步(既server在对数组中元素赋值的过程中,client端绝对不能读)。

解决思路:

       首先,在不加锁的情况下,为同步数据的读写,共享内存多申请一个int变量,作为数据读写权限的标志位(0-空闲,此时生产者可写,1-繁忙,两者均不能访问,2-新数据,此时消费者可读);Server/Client在读取标志位判断自己得到权限后首先应该修改标志位以告诉对方自己要对数据进行操作,然对方等待。

       两个程序配合正常,Server/Client进程中看到的分享内存的首地址是不同的,因为操作系统会为每个单独运行的程序分配自己的进程空间,进程空间以一段地址来表示,每个进程只能运行和操作分配给他的那段地址空间,而不能跨进程地址访问(出于安全考虑);而共享存储的目的也是为了让这样的进程能相互共享数据,内核会为共享存储空间单独开辟一段空间(独立于任何进程),而挂接到该共享存储空间的进程则将共享空间的实际地址映射到自己的地址空间中,这样进程对共享空间的访问就和对自己空间访问一样,也因为这样所以每个进程看到的共享内存的地址是不同的(只看到映射后的地址)。以上为个人理解,具体还需参查资料来确认。

实际效果如图所示:

image

可以看到,在不采用任何缓冲的情况下,Server端每写一个数组,都需要等待Client端去读取后才能继续写,这样两个程序都无法高效运行,空转、轮询和等待耗费大部分时间。

改进,采用一个长度为1000的循环队列来缓存数据,并引入两个游标。不加锁实现两个程序对一个队列的异步读取操作,通过定义两个数组游标来实现(readIndex,writeIndex),一个表示消费者可读取数据的起始位置,一个表示为生产者可写数据的起始位置;

    对于生产者,能操作的队列范围为[writeIndex, readIndex-1)

    对于消费者,能操作的队列范围为[readIndex, writeIndex)

    为方便判断队列状态,生成者最大可写数据包数为(队列长度-1),那么当readIndex == writeIndex 代表队列空,(writeIndex + 1) % 队列长度 == readIndex代表队列满,其他情况代表消费者可以对队列进行写操作;

    而消费者只要在writeIndex != ReadIndex的情况下都可以对队列进行读操作。

    这里,消费者和生产者需要在数据操作完之后才修改相应游标。

    方法2和方法3时间对比:

    明显后者效率要远远高于前者,采用队列的方法在访问一百万条数据只需18秒cpu时间(用户时间也仍然需要18s,应该是对文件写操作耗时),但方法2在优化过后访问一千条数据都需要4秒cpu时间,十万条则需要6分40秒。

改进后效果如图所示:

image

读取一百万条数据也只要18秒的时间,速度提升非常明显。

另外关于性能优化:程序在运行时空转会耗费大量时间,以消费者为例,轮到它执行的时候会不断轮询对应标志位看自己是否可读,可如果生产者还没有生产数据的时候这种轮询其实是无谓的,还暂用时间片,这个时候消费者可以主动让出时间休眠一会,等待对方生产数据再来读取;对于生产者也是一样的道理。在加入休眠后发现程序的性能都提高了很多。还有包括使用字符串拼接这种自定义缓存都会降低程序效率。

附代码:

***********************myshare.h************************

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <memory.h>
#include <signal.h>
#include <string.h>

#define SHM_MODE (SHM_R | SHM_W | IPC_CREAT)
#define PATHNAME "/etc/ssh/sshd_config"
#define CLASSID 1
#define ITEMLEN    100
#define QEUELEN 1000

#define QUIT 1

typedef struct _stItem
{
    unsigned int items[ITEMLEN];
}stItem;

typedef struct _stctrl
{
    int quit;
    int windex;
    int rindex;
}stCtrl;

typedef struct _stCItem
{
    stItem item[QEUELEN];
    int quit;
    int windex;
    int rindex;
}stCItem;

 

 

*************producer.c****************

#include "myshare.h"

/* 自定义接受键盘信号处理函数(ctrl+c,ctrl+\) */
static void sig_quit(int signo);
/* 标志位,如果有键盘中断置1,否则为0 */
int quit;

int main(int argc, char *argv[])
{
    unsigned long int i;
    int idx;
    /* 共享存储的标志ID */
    int shmid;
    /* 共享存储指针 */
    stCItem *shmptr;
    /* 用来生成标志ID的KEY */
    key_t shmkey;
    /* 使用ftok函数拉生产KEY */
    if ( (shmkey = ftok(PATHNAME, CLASSID)) == -1)
        return my_error_sys("ftok error");

    /* 根据KEY来申请共享存储,确保server/client挂载相同空间 */
    if ( (shmid = shmget(shmkey, sizeof(stCItem), SHM_MODE)) < 0)
        return my_error_sys("shmget error");
    /* 连接共享存储 */   
    if ( (shmptr = shmat(shmid, 0, 0)) == (void *)-1)
        return my_error_sys("shmat error");
    /* 注册处理函数,监听键盘信号 */
    if (signal(SIGINT, sig_quit) == SIG_ERR)
        return my_error_sys("signal error");
    if (signal(SIGQUIT, sig_quit) == SIG_ERR)
        return my_error_sys("signal error");
    /* server每次启动先初始化共享存储 */
    memset((void *)shmptr, 0, sizeof(stCItem));
    i = 0;
    quit = 0;
    printf("produce data start...\n");
    while (!quit && !shmptr->quit)
    {
        if ((shmptr->windex + 1) % QEUELEN != shmptr->rindex)
        {
            /* 如果队列有空槽则生产数据 */
            for (idx = 0; idx < ITEMLEN; ++idx)
                ((shmptr->item)[shmptr->windex].items)[idx] = i;
            ++i;
            /* 写完数据后修改游标 */
            shmptr->windex = (shmptr->windex + 1) % QEUELEN;
        }
        else
            /* 避免空转,主动放弃时间片 */
            usleep(1);
    }
    /* 通知对方退出 */
    shmptr->quit = 1;
    /* 清除对应共享存储ID */
    if (shmctl(shmid, IPC_RMID, 0) < 0)
        return my_error_sys("shmctrl data error");
    printf("produce datas i : %d\n", i);
    printf("produce data stop...\n");
    return 0;
}

int my_error_sys(const char *pstr)
{
    printf(pstr);
    printf("\n");
    return -1;
}
static void sig_quit(int signo)
{
    quit = 1;
}

 

****************consumer.c********************

#include "myshare.h"
static void sig_quit(int signo);
int quit;
int main(int argc, char *argv[])
{
    unsigned long int idx;
    unsigned long int i;
    unsigned long int datanum;

    /* 共享存储的标志ID */
    int shmid;
    /* 共享存储指针 */
    stCItem *shmptr;
    /* 用来生成标志ID的KEY */
    key_t shmkey;
    FILE *fp;
    char *resultfile = "data.txt";
    /* 使用参数来控制读取的数据量,方便测试 */
    if( argc != 2)
        return my_error_sys("usage: consumer <data number>");
    sscanf(argv[1], "%d", &datanum);   

    if ( (fp = fopen(resultfile, "w")) == NULL )
        return my_error_sys("open file error");
    /* 使用ftok函数拉生产KEY */
    if ( (shmkey = ftok(PATHNAME, CLASSID)) == -1)
        return my_error_sys("ftok error");

    /* 根据KEY来申请共享存储,确保server/client挂载相同空间 */
    if ( (shmid = shmget(shmkey, sizeof(stCItem), SHM_MODE)) < 0)
        return my_error_sys("shmget error");
    /* 连接共享存储 */   
    if ( (shmptr = shmat(shmid, 0, 0)) == (void *)-1)
        return my_error_sys("shmat error");
    /* 注册处理函数,监听键盘信号 */
    if (signal(SIGINT, sig_quit) == SIG_ERR)
        return my_error_sys("signal error");
    if (signal(SIGQUIT, sig_quit) == SIG_ERR)
        return my_error_sys("signal error");

    quit = 0;
    i = 0;
    memset((void *)shmptr, 0, sizeof(stCItem));
    printf("start consume data....\n");
    while (!quit)
    {
        /* 如果队列有数据则消费数据 */
        if( shmptr->rindex != shmptr->windex )
        {
            for (idx = 0; idx < ITEMLEN; ++idx)
            {
                fprintf(fp, "%u\t", (shmptr->item)[shmptr->rindex].items[idx]);
            }
            /* 用换行符刷新缓存区 */
            fprintf(fp,"\n");
            /* 读取完数据后修改游标 */
            shmptr->rindex = (shmptr->rindex + 1) % QEUELEN;
            ++i;
            if (i >= datanum)
                break;
        }
        else
            /* 避免空转,主动放弃时间片 */
            usleep(1);
    }
    /* 通知对方退出 */
    shmptr->quit = 1;
    fclose(fp);
    /* 清除对应共享存储ID */
    if (shmctl(shmid, IPC_RMID, 0) < 0)
        return my_error_sys("shmctl data error");
    printf("consumer datas i : %d\n", i);
    printf("quit consume...\n");
    return 0;
}

int my_error_sys(const char *pstr)
{
    printf(pstr);
    printf("\n");
    return -1;
}

static void sig_quit(int signo)
{
    quit = 1;
}

posted @ 2010-07-28 17:53 欧拉西欧Oracio 阅读(217) 评论(0) 编辑


2010年7月15日

来到公司实习正好一周了,这一周都听从导师吩咐,主要在熟悉linux环境。因为之前都没有在linux下搞过开发,平时也只是自己心血来潮装来玩玩。现在真正要搭建环境时发现很纠结,现在总结一下过程。

1.环境:虚拟机vmware5.5 + SUSE Linux Enterprise Server10

使用虚拟机来装Linux有很多的方便,一是可以方便上网查找资料和下载软件,二是可以不断的删除和安装镜像,不用考虑多系统并存的引导问题。虚拟机的安装过程就和一般windows软件一样,不过需要到网上搜相应版本的序列号进行破解。下面总结SLES10的安装事项以及如何将虚拟机中的Linux系统和本机windows共享文件夹,接着总结源码安装MySql和Apache的过程。

2.Linux安装

版本:SUSE Linux Enterprise Server 10

(1)首先需要下载安装镜像,可以选择DVD版本的单个ISO文件,也可以选择CD版本的四个ISO文件,可到官网和ChinaUnix上下载。

(2)在vmware中新建一个对应版本的linux虚拟机(版本影响不大,只要内核选择2.6的就行),然后在界面中选择编辑虚拟机配置,在CD-ROM一项中选择使用ISO镜像,并选择镜像文件,网卡使用NAT方式,其他配置默认就可以了。然后启动虚拟机开始安装Linux。

(3)因为在虚拟机中安装Linux,所以可以全部选择默认配置,分区也选择自动,但是在包安装时要选择必要的包,这里我们在默认的情况下添加C/C++ 编译器包的安装,方便之后的源码安装其他软件,其余按照提示进行就可以完成安装了。如果使用的多个CD镜像,在安装的过程中会提示更换光盘,这个时候只要点击vmware界面右下角状态栏

image 中的光驱来更换镜像文件就可以了。

(4)完成安装后直接启动进入Linux系统,然后先安装vmware-tools(这个很重要,设置界面分辨率和文件共享必须)。安装过程就是在进入系统后惦记vmware界面中的虚拟机->安装vmware-tools,之后虚拟机就会自动在Linux系统中挂载一个光盘,里面有两个文件分别是RPM包和源码包,SLES10可以直接右键选择安装RPM包,安装过程就是一路回车就OK了。

源码安装vmware-tools的方法就是先将源码包解压,然后在终端进入该文件夹,输入如下命令

>./configure [--prefix=/usr/local/vmware-tools]

>make

>make install

(5)Linux和windows共享文件夹设置,前提就是必须安装了vmware-tools,然后已root账号的身份执行命令vmware-config-tools.pl,如果是以源码编译安装vmware-tools的话可能系统找不到vmware-config-tools.pl(在configure的时候使用了prefix选项),这个时候就需要到安装目录(prefix选项制定的目录)中去寻找该脚本,然后执行。执行完之后就会在/mnt下面多出一个hgfs目录,这样就完成虚拟机系统中的设置,接下来就是vmware界面中编辑虚拟机配置

image---->image

在选项中选择共享文件夹,然后在右边编辑框加入要共享的文件并命名(例如Share)

这个时候在Linux系统中的/mnt/hgfs下就能看到Share文件夹了。至此就完成了整个共享文件夹的简单设置(其他设置还包括Linux下对windows文件夹的读写权限等)。

 

3.MySql安装

版本:5.1.48

前面设置了共享文件夹,现在就可以在windows下下载对应版本的MySql源码包放到Share文件夹下,再在Linux下安装MySql

(1)解压源码包,如果是图形界面,则解压很方便,如果是文本界面使用如下命令:

> tar -zxvf mysql-5.1.48.tar.gz
>  cd mysql-5.1.48

(2)添加用户和组

>  groupadd mysql
>  useradd -g mysql mysql

(3)配置以及编译安装

>  ./configure --prefix=/usr/local/mysql --with-charset=gbk
>  make
>  make install
>  cp support-files/my-medium.cnf /etc/my.cnf

其中my-medium.cnf为mysql提供的一个简单配置文件,可以按照自己的需求对其进行修改

(4)设置自启动
> cp support-files/mysql.server /etc/rc.d/init.d/mysqld
> chmod 700 /etc/rc.d/init.d/mysqld
> chkconfig --add mysqld
> chkconfig --level 345 mysqld on

mysql.server为mysql的服务器程序,/etc/rc.d/init.d也可写成/etc/init.d,因为rc.d本身是个链接,链接的对象也是init.d。chkconfig为服务配置命令,需要已管理员身份运行。

(5)数据库初始化
> cd /usr/local/mysql
> /usr/local/mysql/bin/mysql_install_db --user=mysql
> chown -R root .
> chown -R mysql var
> chgrp -R mysql .
> /usr/local/mysql/bin/mysqld_safe --user=mysql &

(6)启动数据库 建立快捷方式
> /etc/rc.d/init.d/mysqld start

> ln -s /usr/local/mysql/bin/mysql /sbin/mysql
> ln -s /usr/local/mysql/bin/mysqladmin /sbin/mysqladmin

> PATH=$PATH:/usr/local/mysql/bin
> export PATH
> echo "/usr/local/mysql/lib/mysql" >> /etc/ld.so.conf
> ldconfig

ldconfig为动态链接库的配置程序,在修改/etc/ld.so.conf后都要运行一下ldconfig,在完成前面5步后,第6步可在需要的时候再进行配置

(7)给root加个密码123456,键入以下命令:
>/ 安装路径/bin/mysqladmin –h localhost -u root password 123456

测试数据库
>/安装路径/bin/mysql -u root -p123456
如果出现
mysql>

表示安装成功。

如果进行了第六步后,那么可以直接命令行输入mysql和mysqladmin等命令而不用加上路径,链接服务器的命令中-p123456 表示以密码123456登录,这种方式不推荐,因为密码是以明文出现,最好使用如下方式

>mysql –h localhost –u root  -p

Enter Password:******

4.Apache安装

版本:2.2.15

 安装Apache过程中容易出现的问题就是依赖的包未安装,主要就是apr、apr-util和pcre。APR(Apache portable Run-time libraries,Apache可移植运行库)原本为Apache的一部分后成为独自子项目,目的是为Apache提供跨平台的支持。要安装Apache就必须先安装前面所说的几个包。为方便可以选择2.2.15的版本,这个版本的安装包里面包括了必须的apr包,而不用单独安装,也可避免版本之间的依赖问题,安装过程与Mysql类似,为:

>./configure --prefix=/usr/local/Apache-2.2.15/

>make

>make install

 

安装完成后需要运行服务器,服务器程序httpd在/usr/local/Apache-2.2.15/bin下,需要运行

>httpd start

在浏览器中键入http://localhost/,如果看到

It Works!

那就代表安装成功了,这个时候可以参考Mysql的安装过程配置Apache服务器的自启动和链接。

5.一周回顾

总结得出这个星期就是在不断的在Linux下安装、删除、安装、删除、安装中度过的,现在开始对Linux慢慢熟悉了,似乎也喜欢上在Linux下开发了。为了开发方便还安装了Linux下的C/C++IDE-->codeblocks,安装过程还是configure->make->make install,不过需要主要依赖包的安装

GTK->wxWidgets->codeblocks

从网络上下载GTK包安装后再安装wxWidget,可似乎总提示没有安装GTK,使用--with-gtk-prefix选项也没用,后来直接安装SUSE光盘ISO上的GTK就可以,也许是环境变量没有设置的问题,以后再研究吧,装完wxWidgets后就可以安装了codeblocks了,如果提示没有安装依赖的wxWidgets的话就在configure的时候使用--with-wx-prefix或者--with-wx-path就可以了。

posted @ 2010-07-15 17:47 欧拉西欧Oracio 阅读(86) 评论(0) 编辑


  

posts - 2, comments - 0, trackbacks - 0, articles - 0

Copyright © 欧拉西欧Oracio