当定义CONFIG_CMD_NET和CONFIG_CMD_PING,编译之后执行ping命令,告警没有找到以太网。

因此,需要打开U-boot的网络功能, u-boot-sunxi-sunxi中没有找到明显的网络驱动代码,或许有通用的驱动,但可以获得资料的途径有限,再说我是个初学者,平时工作属于自动控制类,网络方面很菜,因此想通过修改一个网络驱动,进行一次初步学习,想到就开工...

边做边写,恐怕会比较乱。

开发环境:   1、笔记本RHEL5,安装编译器arm-none-eabi-版本4.7.2; 编辑器Vim;minicom2.1

2、台式机XPsp3,安装SourceInsight3.5

基本思路:  1、找到u-boot内网络代码运行的轨迹,初始化、数据交换的驱动接口等等

2、实现一个驱动,加入到这个运行轨迹中,设备初始化,数据读写等等

准备工作:   1、找到芯片资料,这个比较坑,只能找到RTL8021CP的PDF,至于CPU芯片资料,

×,那简直不能称为资料,看看三星处理器资料,为啥别人能做大,不是没有原因的。

2、Cubieboard原理图一份,这个好弄,人手一份呀

3、网线准备了两根,一个常用上网的,一根交叉线。路由器一只。

在没有研究清楚硬件连接之前,这样准备应该比较充足了。

4、下个新版本编译器,找了一个arm-none-eabi-的编译器,版本4.7.2,估计是目前最高版本。

下载:http://www.codesourcery.com/sgpp/lite/arm

选择arm处理器,linux版本,点进去之后需要用邮箱注册,下载地址会发到邮箱。

5、因为没有CPU资料,需要寻找一个A10的网络驱动代码,在支持Cubieboard的内核中找到了。

下载:http://linux-sunxi.org/Cubieboard/Cubieboard_Linux-3.9

这个驱动是linux下的,需要修改一下或参考其操作硬件的过程,以便在u-boot内运行。

一、环境建立,初步编译

考虑到这次需要参考其他开发板或CPU的网络驱动,因此用SourceInsight3.5建立一个u-boot-sunxi-sunxi的工程,建立关联关系,方便代码查阅。建立方法很简单,不懂的可以网上搜索一下。建立工程时不要删除其他代码,全部使用。

linux下编译环境设置,将下载的编译器解压到 /usr/local/arm/目录,解压之后目录是arm-2012.09

设置环境变量,在RHEL5系统内,在/ect/profile的末尾加上一句:

export   PATH=$PATH:/usr/local/arm/arm-2012.09/bin

保存之后,执行:source /ect/profile 或者logout,或者重启机器。

回到bash,输入arm再按Tab键,可以看到编译工具链列出来了,执行

arm-none-eabi-gcc   -v,可以看到版本是4.7.2

其他系统编译器安装设置可以在网上搜索,有很多文章会提到。

进入u-boot-sunxi-sunxi目录,修改Makefile内容,找到 CROSS_COMPILE ?=  这一行

改为:CROSS_COMPILE ?= arm-none-eabi-

进入 u-boot-sunxi-sunxi/arch/arm,修改config.mk内容,也是这一句 CROSS_COMPILE ?=

改为:CROSS_COMPILE ?= arm-none-eabi-

回到u-boot-sunxi-sunxi目录,执行:

make  distclean                     //清除之前编译的内容

make  cubieboard_config     //设置板子

make  -j4                                //编译,-j4表示多线程编译,可以使用-j2,如果是虚拟机可以不用这个参数,

//如果电脑配置比较好,可以使用 -j8

等待一会,不出意外,编译完成,写入SD卡,到板子上运行,可以看到串口输出信息。

二、U-boot网络模块分析

1、网络初始化

之前曾经分析过主循环,在主循环main_loop()调用之前就是初始化。

在文件\u-boot-sunxi-sunxi\arch\arm\lib\board.c, 函数board_init_r()内:

...........

#if defined(CONFIG_CMD_NET)

        puts("Net:   ");

        eth_initialize(gd->bd);

        #if defined(CONFIG_RESET_PHY_R)

        debug("Reset Ethernet PHY\n");

        reset_phy();

        #endif

        #endif

...........

这一段代码可以看出,要执行网络初始化,需要定义CONFIG_CMD_NET。

因此在\u-boot-sunxi-sunxi\include\configs\sunxi-common.h 末尾加一句:

#define  CONFIG_CMD_NET

再来看看函数eth_initialize() 的内容,在文件 \u-boot-sunxi-sunxi\net\eth.c内:

.................

if (board_eth_init != __def_eth_init) {

             if (board_eth_init(bis) < 0)

             printf("Board Net Initialization Failed\n");

       } else if (cpu_eth_init != __def_eth_init) {

             if (cpu_eth_init(bis) < 0)

                    printf("CPU Net Initialization Failed\n");

       } else

             printf("Net Initialization Skipped\n");

.................

如果只定义CONFIG_CMD_NET,在上电时就会打印 Net Initialization Skipped,执行了最后一个else的内容,因此,需要完成网络初始化,需要实现函数board_eth_init()或者cpu_eth_init()

看代码,这两个函数谁等于__def_eth_init就执行谁,在看看__def_eth_init是啥,也在eth.c这个文件内

static int   __def_eth_init(bd_t *bis)

       {

              return -1;

       }

       int cpu_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));

       int board_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));

可见,实现__def_eth_init的“alias  ”,bash命令里面有个alias,如果你用过就明白这是什么意思了,实现它,就可以进行初始化啦。

想知道weak, alias是什么意思,请围观这位大侠的博客 http://blog.chinaunix.net/uid-20272712-id-1969771.html

以太网模块在A10内,A10的手册称之为WEMAC模块,因此,我们需要实现cpu_eth_init函数,表明eathnet模块在CPU内部。

在文件\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\board.c内增加函数cpu_eth_init,内容如下:

#ifdef   CONFIG_SUN4I_WEMAC

int cpu_eth_init(bd_t *bis)

       {

              return   sun4i_wemac_initialize(bis);

       }

       #endif

2、如何实现网络模块

网络模块已经成熟了,我们并不需要增加很多代码,只需要实现对硬件的操作就可以了。

基本的操作大约就几个:初始化、打开、关闭、接收、发送、挂起

分析\u-boot-sunxi-sunxi\net目录内的代码,发现网络的数据结构定义在\u-boot-sunxi-sunxi\include\net.h内

struct eth_device {

           char name[16];                                                                              //网络名

           unsigned char enetaddr[6];                                                         //以太网地址

           int iobase;                                                                                       //io基址?

           int state;                                                                                          //设备状态

int  (*init) (struct eth_device *, bd_t *);                                       //网络设备初始化

           int  (*send) (struct eth_device *, void *packet, int length);     //发送数据

           int  (*recv) (struct eth_device *);                                                  //接收数据

           void (*halt) (struct eth_device *);                                                 //挂起

           #ifdef CONFIG_MCAST_TFTP

           int (*mcast) (struct eth_device *, u32 ip, u8 set);

           #endif

           int  (*write_hwaddr) (struct eth_device *);                                 //写硬件地址?这个是啥?

           struct eth_device *next;

           int index;

           void *priv;

      };

struct eth_device结构体定义了网络设备的基本操作,从面向对象的角度来说,只要在驱动代码内将struct eth_device的init,send,recv,halt,write_hwaddr这几种方法实现,就可以实现网络的操作了,至于数据收回来之后在上层的解析方式那就不是驱动关心的了。任务变得简单了,实现这几种操作便可

再看struct eth_device结构体下面这几个函数:

extern int eth_initialize(bd_t *bis); /* Initialize network subsystem */

      extern int eth_register(struct eth_device* dev);/* Register network device */

      extern int eth_unregister(struct eth_device *dev);/* Remove network device */

      extern void eth_try_another(int first_restart); /* Change the device */

      extern void eth_set_current(void);  /* set nterface to ethcur var */

都在\u-boot-sunxi-sunxi\net\eth.c内实现,分析一下eth_register这个函数(其他的就不多说了),如下,

int  eth_register( struct eth_device  *dev )

      {

             struct eth_device *d;

             static int index;

assert( strlen(dev->name)  <  sizeof(dev->name) );

if (!eth_devices) {

//注册之前eth_devices应该初始化为NULL,在当前文件的函数int  eth_initialize( bd_t *bis ) 中,

//开始几句代码就这样初始化了,但是这个函数eth_initialize将调用我们自己写的cpu_eth_init(),

//并未调用eth_register,因此可以预见,自己写的函数cpu_eth_init将要调用eth_register对网络进行注册,

//实际工作还是要自己实现

                   eth_current = eth_devices = dev;

                   eth_current_changed();

              } else {

                   for (d = eth_devices; d->next != eth_devices; d = d->next)

                         ;

                   d->next = dev;

              }

dev->state  =  ETH_STATE_INIT;     //网络设备状态为初始化

              dev->next    =  eth_devices;               //链表的下一个指向自己,为啥呢?

              dev->index  =  index++;                       //设备个数增加了

return 0;

       }

三、实现网络驱动

这些内容都将实现在sun4i_wemac.c和sun4i_wemac.h内。这两个文件都在\u-boot-sunxi-sunxi\driver\net\目录下。

1、数据结构

经过上面分析,可以了解到,假设自己定义一个网络设备的数据结构,那么这个结构大致如下,下面直接写一个,实际代码还要推敲推敲:

struct   sun4i_wemac_dev {

void   *wemac_base;    // A10内部wemac模块的基地址

void   *gpio_base;          //wemac模块使用的gpio的基地址,去看看原理图,实际使用A10的PA口

//接收发送buffer管理,使用队列

unsigned int  rx_head;

             unsigned int  rx_tail;

             unsigned int  tx_head;

             unsigned int  tx_tail;

void   *rx_buffer;

             void   *tx_buffer;

struct   eth_device   netdev;      //这就是上面的以太网设备,这个一定要有

             unsigned short     phy_addr;    //PHY地址,就是板子上RTL8201CP的地址

       };

有的人又问啦,为什么要自己定义一个数据结构呢?net.h里面不是已经有一个struct eth_device了吗?仔细想一下,struct eth_device是u-boot定义的数据结构,里面的每一个成员可能在u-boot的其他网络模块代码中被使用修改,我们并不完全知道所有代码对struct eth_device的操作,因此需要自己定义一个结构,提供这个接口就可以了。用一句话总结就是:struct eth_device是定义给u-boot的网络模块使用的,用户得定义自己的设备,以此隔离了驱动代码与u-boot代码。

2、实现初始化

就是上面提到的cpu_eth_init()内调用的sun4i_wemac_initialize()函数,只写一个思路,具体代码还需要推敲:

int   sun4i_wemac_initialize(  bd_t  *bis  )

{

struct   sun4i_wemac_dev  *wemac_dev;    //自己定义的结构

struct   eth_device   *netdev;                            //u-boot已经定义的结构

wemac_dev  =  malloc( sizeof ( struct sun4i_wemac_dev  ) );   //分配内存

           if ( wemac_dev  ) {

                  printf("Error: Failed to allocate memory for WEMAC\n");

                  return -1;

           }

           memset(wemac_dev , 0, sizeof( struct    sun4i_wemac_dev  ));

netdev = &wemac_dev ->netdev;

/*****************************/

初始化发送、接收管理队列

若还需要其他功能,可以增加到数据结构struct   sun4i_wemac_dev中,并在此初始化,

写文章时,代码还没写,留待后续补全

/*****************************/

wemac_dev ->wemac_base =  (void *)EMAC_BASE;

wemac_dev ->gpio_base      =  (void *)PA_BASE;

wemac_dev ->phy_addr        = WEMAC_PHY;

//以下就是要实现的网络设备结构体内的“方法”,就是驱动代码中主要的几个函数,

//可以参考从内核拷贝过来的驱动是如何实现的:

netdev->init                      = sun4i_wemac_init;      //注意这个初始化函数跟当前函数是不同的,这个函数

//主要初始化wemac模块和RTL8201芯片

           netdev->halt                     = sun4i_wemac_halt;

           netdev->send                  = sun4i_wemac_send;

           netdev->recv                    = sun4i_wemac_recv;

           netdev->write_hwaddr   = sun4i_wemac_write_hwaddr;

.....................其他初始化...........

eth_register( netdev );    // 最后将网络设备注册,这样u-boot就能使用驱动程序啦

// 注册之后,eth.c内的全局变量eth_current 指向wemac_dev ->netdev,

//这是在u-boot任何代码使用eth_current,

// 都表示使用的是现在初始化的模块

return 0;

}

3、其他模块的实现

看看eth.c内怎么实现接收和发送接口的,就可以知道驱动代码应该怎么写了

int eth_send(void *packet, int length)

       {

             if ( !eth_current )                         //eth_current是全局指针,指向驱动初始化的结构体wemac_dev ->netdev

                  return -1;

return eth_current->send(eth_current, packet, length);  //注册之后,实际调用就是sun4i_wemac_send;

       }

int eth_rx(void)

       {

            if (!eth_current)

                   return -1;

return eth_current->recv(eth_current);  //注册之后,实际调用就是sun4i_wemac_recv;



       }

因此,可以知道,驱动代码实现的接收发送形式如下:

static  int   sun4i_wemac_send( struct  eth_device  *dev,  void  *packet,   int  length )

{

struct   sun4i_wemac_dev  wemac_dev  = 
to_wemac(dev);

//功能如何实现,可以参考内核代码的驱动。

}

static  int   sun4i_wemac_recv( struct  eth_device   *dev )

{

struct   sun4i_wemac_dev  wemac_dev  =  to_wemac(dev);

//功能如何实现,可以参考内核代码的驱动。

}

由于我们自己定义的结构体是struct   sun4i_wemac_dev,而传过来的参数是全局指针eth_current,

根据上面的分析,eth_current指向struct   sun4i_wemac_dev结构内的struct   eth_device   netdev,

因此要获得指向struct   sun4i_wemac_dev的指针需要使用linux内核常用的手段,“容器”的概念就出来了,

看代码定义:

struct   sun4i_wemac_dev  wemac_dev  =  container_of(  dev ,  struct   sun4i_wemac_dev ,  netdev);

就可以获得注册之前申请的结构体的首地址,如何办到的呢?

来看看这个在linux内核中常用的宏:

#define   container_of( ptr, type, member )       ( {   \

                                                                                           const typeof( ((type *)0)->member ) *__mptr  =  (ptr); \

                                                                                           (type *)( (char *)__mptr - offsetof(type,member) ); }  )

#define   offsetof( TYPE, MEMBER )     ( (size_t)  & ((TYPE *)0)->MEMBER )

这是一个很神奇的宏,可以把它用于自己以后的C代码开发中,如果使用其他编译器,这里面的关键字要改一下。

意思就是,dev指向的内存地址,减掉它在struct   sun4i_wemac_dev中的偏移量,就获得了初始化时定义的

struct   sun4i_wemac_dev 指针

偏移量的获取很简单 (( struct   sun4i_wemac_dev *) 0)->netdev  就是偏移量;我们知道使用 -> 操作时,获得指向当前结构体的某一个成员的地址, 而这个成员的地址与结构体首地址的差值,就是它在结构体内的偏移量,如果把结构体的首地址设置为0,结果妙不可言。

由于很多函数都要用到这个宏,因此可以再写成下面这样:

#define  to_wemac(_nd)    container_of( _nd,  struct   sun4i_wemac_dev, netdev)

上面的定义就可以这样:  struct   sun4i_wemac_dev  wemac_dev  =  to_wemac(dev);

另外3个个函数,实现方式也是一样,先去分析eth.c内调用,一并都分析一下吧:

int eth_init(bd_t *bis)

       {

              struct eth_device *old_current, *dev;

if (!eth_current) {                   //必须注册成功之后,否则这里判断失败,将打印找不到ethernet

                      puts("No ethernet found.\n");

                      return -1;

              }

/* Sync environment with network devices */

             dev = eth_devices;

             do {

                    uchar env_enetaddr[6];

if ( eth_getenv_enetaddr_by_index( "eth",   dev->index,  env_enetaddr))

memcpy(dev->enetaddr, env_enetaddr, 6);

//这里就是获取IP地址了,从哪里获取呢?这是u-boot解决的问题啦

//熟悉情况的人这时会想起一个文件,就是u-boot移植时说的参数配置文件

//uEnv.txt或boot.scr,怎么获得,需要进一步追查代码流程,这不是网络驱动关心的。

dev = dev->next;

             } while ( dev  !=  eth_devices );          // CB上只有一个以太网设备,因此循环一次就会结束

old_current = eth_current;

            do {

                       debug("Trying %s\n", eth_current->name);

if ( eth_current->init( eth_current, bis )  >=  0 ) { //实际调用的就是函数sun4i_wemac_init ,

//对WEMAC模块和PHY芯片进行初始化

                              eth_current->state = ETH_STATE_ACTIVE;    //初始化成功,处于活动状态标志

return 0;    //成功就返回啦

                      }

                      debug("FAIL\n");

eth_try_another(0);  //不成功,试试另一个,不用分析,这个函数可定会尝试改变全局指针eth_current

             } while (old_current != eth_current);

return -1;

      }

还有一个疑问,那又是什么地方调用int eth_init(bd_t *bis)这个函数呢?借助强大的Source Insight,很快就能找到调用它的地方啦,搜索工程,出来一大片,只需关心文件net.c的调用即可,其他调用是不会被编译进来的,以下两个函数调用了这个初始化,从名字就能看出其大概功能了

int  NetLoop( enum   proto_t protocol );  //这就是读取网络数据的主循环

void  NetStartAgain( void );                        //这个是net重新开始,当然要初始化

再来看看ping命令干了啥,在文件\u-boot-sunxi-sunxi\common\cmd_net.c

#if defined(CONFIG_CMD_PING)

      int do_ping (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

      {

            if (argc < 2)

                 return -1;

NetPingIP = string_to_ip(argv[1]);

            if (NetPingIP == 0)

                   return CMD_RET_USAGE;

if (NetLoop(PING) < 0) {    //这里调用了NetLoop函数,再调用eth_init,再调用自己写的sun4i_wemac_init 

                   printf("ping failed; host %s is not alive\n", argv[1]);

                   return 1;

            }

printf("host %s is alive\n", argv[1]);

return 0;

        }

U_BOOT_CMD(

                                     ping, 2, 1, do_ping,

                                    "send ICMP ECHO_REQUEST to network host",

                                    "pingAddress"

        );

        #endif

至于NetLoop内如何实现数据打包,如何解析,有兴趣的可以继续深入。

void eth_halt(void)

      {

              if (!eth_current)

              return;

eth_current->halt(eth_current); 
//实际就是调用sun4i_wemac_halt,网络设备挂起

eth_current->state = ETH_STATE_PASSIVE; //标志变化

      }

int eth_write_hwaddr(struct eth_device *dev, const char *base_name,  int eth_number)

     {

              unsigned char env_enetaddr[6];

              int ret = 0;

eth_getenv_enetaddr_by_index(base_name, eth_number, env_enetaddr);

if (memcmp(env_enetaddr, "\0\0\0\0\0\0", 6)) {

                      if (memcmp(dev->enetaddr, "\0\0\0\0\0\0", 6) &&  memcmp(dev->enetaddr, env_enetaddr, 6)) {

                                printf("\nWarning: %s MAC addresses don't match:\n", dev->name);

                                printf("Address in SROM is         %pM\n", dev->enetaddr);

                                printf("Address in environment is  %pM\n", env_enetaddr);

                      }

memcpy(dev->enetaddr, env_enetaddr, 6);

              } else if (is_valid_ether_addr(dev->enetaddr)) {

                      eth_setenv_enetaddr_by_index(base_name, eth_number,  dev->enetaddr);

                       printf("\nWarning: %s using MAC address from net device\n", dev->name);

              }

if (dev->write_hwaddr &&  !eth_mac_skip(eth_number)) {

                      if (!is_valid_ether_addr(dev->enetaddr))

                               return -1;

ret = dev->write_hwaddr(dev);//实际就是调用sun4i_wemac_write_hwaddr,将IP地址写入硬件??

              }

return ret;

      }

这三个函数形式如下:

int     sun4i_wemac_init ( struct eth_device *, bd_t * );

      void  sun4i_wemac_halt( struct eth_device * );

      int     sun4i_wemac_write_hwaddr( struct eth_device * );

具体内容如何实现,在没有详细CPU手册的情况下,参考内核驱动代码是最好的选择,如果熟悉内核驱动编程就更好了,那是下一个目标。

四、驱动完成之后的初步测试

测试ping命令

测试tftp命令-----这个命令还没有,下一次学习的目标。

代码还没写,留待后续补全

在U-boot中添加以太网驱动的更多相关文章

  1. Maven项目中添加JDBC驱动

    在pom.xml配置文件中添加: <dependency> <groupId>mysql</groupId> <artifactId>mysql-con ...

  2. spring boot 中添加mongodb支持

    1.添加maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactI ...

  3. web项目中添加MySQL驱动

    1.我这里采用yml文件来配置,yml有配置层次清晰,方便操作的好处: 将application.properties后缀改成yml,即配置文件变成application.yml 我的applicat ...

  4. 在Spring Boot中添加全局异常捕捉提示

    在一个项目中的异常我们我们都会统一进行处理的,那么如何进行统一进行处理呢? 全局异常捕捉: 新建一个类GlobalDefaultExceptionHandler, 在class注解上@Controll ...

  5. idea中添加mysql驱动jia包的方法

    1 将相关jar包拷贝到自己所建的lib 文件夹下  如下图所示 2   选中自己的module  接着选择Project Structure 如下图 3 接着如下图继续操作 如上图完成后  那么我们 ...

  6. win7原版映像中添加usb3.0驱动

    最近用软碟通制作了一个win7原版映像,但是在装新系统的时候发现了一个问题,进入安装界面后,显示没有找到驱动器,但是明明是差了U盘的,通过“shift+f12”调出命令行窗口,输入disk list命 ...

  7. Windows 7原版映像中添加usb3.0驱动

    最近用软碟通制作了一个win7原版映像,但是在装新系统的时候发现了一个问题,进入安装界面后,显示没有找到驱动器,但是明明是差了U盘的,通过“shift+f12”调出命令行窗口,输入disk list命 ...

  8. 在Spring Boot中使用数据缓存

    春节就要到了,在回家之前要赶快把今年欠下的技术债还清.so,今天继续.Spring Boot前面已经预热了n篇博客了,今天我们来继续看如何在Spring Boot中解决数据缓存问题.本篇博客是以初识在 ...

  9. Qt添加驱动——Qt数据库之添加MySQL驱动插件

    Qt数据库之添加MySQL驱动插件(1) 现在可用的数据库驱动只有3种,在Qt中,我们需要自己编译其他数据库驱动的代码,让它们以插件的形式来使用.下面我们就以现在比较流行的MySQL数据库为例,说明一 ...

随机推荐

  1. java web 整合开发王者归来学习总结

    第一章java web开发概述 胖客户端CS,瘦客户端BS(Browser) 网址请求---服务器处理响应-----返回结果-----浏览器显示 CGI可以动态生成页面,但是每个进程都要启动一个CGI ...

  2. C#基础(四)--值类型和引用类型,栈和堆的含义

    本文主要是讨论栈和堆的含义,也就是讨论C#的两种类据类型:值类型和引用类型: 虽然我们在.net中的框架类库中,大多是引用类型,但是我们程序员用得最多的还是值类型. 引用类型如:string,Obje ...

  3. python中math模块常用的方法整理

    ceil:取大于等于x的最小的整数值,如果x是一个整数,则返回x copysign:把y的正负号加到x前面,可以使用0 cos:求x的余弦,x必须是弧度 degrees:把x从弧度转换成角度 e:表示 ...

  4. 前段篇:HTML

    <!DOCTYPE html> 文件开头统一的标准! HTML包含了两部分: head与body  固定的格式. 一.head部分: head部分分为两部分:meta标签与非meta标签: ...

  5. 使用py2exe发布windows平台Python

    一.简介 py2exe是一个将python脚本转换成windows上的可独立执行的可执行程序(*.exe)的工具,这样,你就可以不用装python而在windows系统上运行这个可执行程序.py2ex ...

  6. bzoj 2627: JZPKIL [伯努利数 Pollard-rho]

    2627: JZPKIL 题意:求 \[ \sum_{i=1}^n (n,i)^x [i,n]^y,\ [i,n] = lcm(i,n) \] \(n \le 10^{18},\ x,y\le 300 ...

  7. BZOJ 2034: [2009国家集训队]最大收益 [贪心优化 Hungary]

    2034: [2009国家集训队]最大收益 题意:\(n \le 5000\)个区间\(l,r\le 10^8\),每个区间可以选一个点得到val[i]的价值,每个点最多选1次,求最大价值 线段树优化 ...

  8. SDN第三次作业

    作业链接 阅读文章:http://www.sdnlab.com/19777.html 阅读<重构网络>第一二章 列举openflow1.0的12元组? 入端口 源MAC地址 目的MAC地址 ...

  9. WinSock IOCP 模型总结(附一个带缓存池的IOCP类)

    前言 本文配套代码:https://github.com/TTGuoying/IOCPServer 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了 ...

  10. 初识vps,域名与购买,初步配置

    终于还是到了这一天,不管我们是不是程序员,当我们想拥有自己的一个的博客,当我们想有自己的一个空间,当我们想在网上有一个自己可以随心所欲编写任何不被限制的仅仅是酷炫的效果,当我们想收录自己的技术,经历, ...