linux 条件变量与线程池
条件变量Condition Variables
概述
1. 条件变量提供了另外一种线程同步的方式。如果没有条件变量,程序需要使用线程连续轮询(可能在临界区critical section内)方式检查条件是否满足。由于线程连续忙于轮询检查,这会非常消耗资源,而条件变量是一种实现同样目标不需要轮询的方式。
2. 条件变量总是和互斥锁相结合使用。
3. 条件变量使用示例结构:
Main Thread
|
|
Thread A
|
Thread B
|
Main Thread Join / Continue |
创建和销毁条件变量
pthread_cond_init (condition,attr)
pthread_cond_destroy (condition)
pthread_condattr_init (attr)
pthread_condattr_destroy (attr)
条件变量必须声明为pthread_cond_t,并且使用之前必须初始化。有两种方式初始化条件变量:
1)静态初始化:pthread_cond_t myconvar = PTHREAD_COND_INITIALIZER;
2)动态初始化: pthread_cond_init()。条件变量的id号通过条件变量参数返回于调用线程,这种方式允许设置条件变量的属性。然而,只有一种条件变量属性process-shared,这允许其他进程的线程可见该条件变量。如果使用条件变量属性,那么必须是pthread_condattr_t 类型(为了接受默认值可以指定为NULL)。需要注意的是,并非所有实现提供process-shared属性。
信号等待与信号通知
pthread_cond_wait
(condition,mutex):阻塞调用线程直到特定的条件触发。当互斥量被锁住时该函数应当被调用;当它等待时它将自动释放互斥锁。接收到信号通知和线程被唤醒后,互斥量将自动地被线程锁住。当线程完成任务时,需要手动解锁互斥量。
pthread_cond_signal
(condition)用于唤醒另外一个等待条件变量的线程。互斥量被锁住之后才可调用pthread_cond_signal并且按序解锁用于pthread_cond_wait完成。
pthread_cond_broadcast
(condition)如果多于一个线程处于阻塞等待状态,那么应当使用pthread_cond_broadcast而不是pthread_cond_signal。
建议使用while循环而不是if,这样可以检查一些潜在的问题,例如:如果若干线程在等待同一个唤醒信号,它们将轮询捕获互斥量,它们中的任何一个可以修改条件;由于程序bug,线程接收到错误信号;线程库允许不违反标准的前提下虚假的唤醒一个等待线程。
使用这些函数时,必须正确地加锁解锁互斥变量。
调用pthread_cond_wait前锁定互斥量失败可能导致线程阻塞失败;
调用pthread_cond_signal后解锁互斥量失败可能不允许匹配的pthread_cond_wait完成(即阻塞掉)。
实际上pthread_cond_wait的返回不仅仅是pthread_cond_signal和pthread_cond_broadcast导致的,还会有一些假唤醒,也就是spurious wakeup。
pthread_cond_wait的通常使用方法:
pthread_mutex_lock();
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
为什么在pthread_cond_wait()前要加一个while循环来判断条件是否为假呢?
APUE中写道:
传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。
线程释放互斥量,等待其他线程发给该条件变量的信号(唤醒一个等待者)或广播该条件变量(唤醒所有等待者)。当等待条件变量时,互斥量必须始终为释放的,这样其他线程才有机会锁住互斥量,修改条件变量。当线程从条件变量等待中醒来时,它重新继续锁住互斥量,对临界资源进行处理。
条件变量的作用是发信号,而不是互斥。
wait前检查
对于多线程程序,不能够用常规串行的思路来思考它们,因为它们是完全异步的,会出现很多临界情况。比如:pthread_cond_signal的时间早于pthread_cond_wait的时间,这样pthread_cond_wait就会一直等下去,漏掉了之前的条件变化。
对于这种情况,解决的方法是在锁住互斥量之后和等待条件变量之前,检查条件变量是否已经发生变化。
if(condition_is_false)
pthread_cond_wait();
这样在等待条件变量前检查一下条件变量的值,如果条件变量已经发生了变化,那么就没有必要进行等待了,可以直接进行处理。这种方法在并发系统中比较常见
1.等待函数里面要传入一个互斥量,这个互斥量会在这个函数调用时会发生如下变化:函数刚刚被调用时,会把这个互斥量解锁,然后让调用线程阻塞,解锁后其他线程才有机会获得这个锁。当某个线程调用通知函数时,这个函数收到通知后,又把互斥量加锁,然后继续向下操作临界区。可见这个设计是非常合理的!
2.条件变量的等待函数用while循环包围。原因:如果有多个线程都在等待这个条件变量关联的互斥量,当条件变量收到通知,它下一步就是要锁住这个互斥量,但在这个极小的时间差里面,其他线程抢先获取了这互斥量并进入临界区把某个状态改变了。此时这个条件变量应该继续判断别人刚刚抢先修改的状态,即继续执行while的判断。还有一个原因时防止虚假通知,收到虚假通知后,只要while里面的条件为真,就继续休眠.
参考资料:https://computing.llnl.gov/tutorials/pthreads/#ConVarSignal
http://www.cnblogs.com/leaven/archive/2010/06/03/1750973.html
https://www.cnblogs.com/yuuyuu/p/5140875.html
linux下C 线程池的原理讲解和代码实现(能自行伸缩扩展线程数)
Linux C++线程池框架
Linux的多任务编程-线程池
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 12 int count = ;
int thread_ids[] = {,,};
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv; void *inc_count(void *t)
{
int i;
long my_id = (long)t; for (i=; i<TCOUNT; i++) {
pthread_mutex_lock(&count_mutex);
count++; /*
Check the value of count and signal waiting thread when condition is
reached. Note that this occurs while mutex is locked.
*/
if (count == COUNT_LIMIT) {
pthread_cond_signal(&count_threshold_cv);
printf("inc_count(): thread %ld, count = %d Threshold reached.\n",
my_id, count);
}
printf("inc_count(): thread %ld, count = %d, unlocking mutex\n",
my_id, count);
pthread_mutex_unlock(&count_mutex); /* Do some "work" so threads can alternate on mutex lock */
sleep();
}
pthread_exit(NULL);
} void *watch_count(void *t)
{
long my_id = (long)t; printf("Starting watch_count(): thread %ld\n", my_id); /*
Lock mutex and wait for signal. Note that the pthread_cond_wait
routine will automatically and atomically unlock mutex while it waits.
Also, note that if COUNT_LIMIT is reached before this routine is run by
the waiting thread, the loop will be skipped to prevent pthread_cond_wait
from never returning.
*/
pthread_mutex_lock(&count_mutex);
while (count<COUNT_LIMIT) {
pthread_cond_wait(&count_threshold_cv, &count_mutex);
printf("watch_count(): thread %ld Condition signal received.\n", my_id);
}
count += ;
printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
pthread_mutex_unlock(&count_mutex);
pthread_exit(NULL);
} int main (int argc, char *argv[])
{
int i, rc;
long t1=, t2=, t3=;
pthread_t threads[];
pthread_attr_t attr; /* Initialize mutex and condition variable objects */
pthread_mutex_init(&count_mutex, NULL);
pthread_cond_init (&count_threshold_cv, NULL); /* For portability, explicitly create threads in a joinable state */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&threads[], &attr, watch_count, (void *)t1);
pthread_create(&threads[], &attr, inc_count, (void *)t2);
pthread_create(&threads[], &attr, inc_count, (void *)t3); /* Wait for all threads to complete */
for (i=; i<NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf ("Main(): Waited on %d threads. Done.\n", NUM_THREADS); /* Clean up and exit */
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv);
pthread_exit(NULL); }
翻译资料:https://computing.llnl.gov/tutorials/pthreads/#ConVarSignal
线程池
上面之所以会谈到条件变量,有两个原因,其一线程池的实现需要条件变量方面的知识,其二因为它的实现牵涉到一些细节,理解条件变量有一定的困难,如果不理解它与互斥锁结合使用的实现原理,也就无法正确使用条件变量。
#ifndef THREADPOOL_H
#define THREADPOOL_H
/*
*线程池包括:n个执行任务的线程,一个任务队列,一个管理线程
1、预先启动一些线程,线程负责执行任务队列中的任务,当队列空时,线程挂起。
2、调用的时候,直接往任务队列添加任务,并发信号通知线程队列非空。
3、管理线程负责监控任务队列和系统中的线程状态,当任务队列为空,线程数目多且很多处于空闲的时候,便通知一些线程退出以节约系统资源;当任务队列排队任务多且线程都在忙,便负责再多启动一些线程来执行任务,以确保任务执行效率。
*
*/
#include <pthread.h> typedef struct threadpool_task_t
{
void *(*function)(void *);
void *arg;
} threadpool_task_t; typedef struct threadpool_t
{
pthread_mutex_t lock;// mutex for the taskpool
pthread_mutex_t thread_counter;//mutex for count the busy thread
pthread_cond_t queue_not_full;
pthread_cond_t queue_not_empty;//任务队列非空的信号
pthread_t *threads;//执行任务的线程
pthread_t adjust_tid;//负责管理线程数目的线程
threadpool_task_t *task_queue;//任务队列
int min_thr_num;
int max_thr_num;
int live_thr_num;
int busy_thr_num;
int wait_exit_thr_num;
int queue_front;
int queue_rear;
int queue_size;
int queue_max_size;
bool shutdown;
}threadpool_t; threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size); int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg); /**
* @function void *threadpool_thread(void *threadpool)
* @desc the worker thread
* @param threadpool the pool which own the thread
*/ void *threadpool_thread(void *threadpool);
/**
* @function void *adjust_thread(void *threadpool);
* @desc manager thread
* @param threadpool the threadpool
*/ void *adjust_thread(void *threadpool);
/**
* check a thread is alive
*/ bool is_thread_alive(pthread_t tid); int threadpool_destroy(threadpool_t *pool); int threadpool_free(threadpool_t *pool); int threadpool_all_threadnum(threadpool_t *pool); int threadpool_busy_threadnum(threadpool_t *pool); #endif // THREADPOOL_H
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include "threadpool.h"
#define DEFAULT_TIME 10 // 领导定时检查队列、线程状态的时间间隔
#define MIN_WAIT_TASK_NUM 10 // 队列中等待的任务数>这个值,便会增加线程
#define DEFAULT_THREAD_VARY 10 //每次线程加减的数目 //创建线程池
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
{
threadpool_t *pool = NULL;
do{
if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL)
{
printf("malloc threadpool fail");
break;
}
pool->min_thr_num = min_thr_num;
pool->max_thr_num = max_thr_num;
pool->busy_thr_num = ;
pool->live_thr_num = min_thr_num;
pool->queue_size = ;
pool->queue_max_size = queue_max_size;
pool->queue_front = ;
pool->queue_rear = ;
pool->shutdown = false;
pool->threads = (pthread_t *)malloc(sizeof(pthread_t)*max_thr_num);
if (pool->threads == NULL)
{
printf("malloc threads fail");
break;
}
memset(pool->threads, , sizeof(pool->threads));
pool->task_queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t)*queue_max_size);
if (pool->task_queue == NULL)
{
printf("malloc task_queue fail");
break;
}
if (pthread_mutex_init(&(pool->lock), NULL) !=
|| pthread_mutex_init(&(pool->thread_counter), NULL) !=
|| pthread_cond_init(&(pool->queue_not_empty), NULL) !=
|| pthread_cond_init(&(pool->queue_not_full), NULL) != )
{
printf("init the lock or cond fail");
break;
}
/**
* start work thread min_thr_num
*/
for (int i = ; i < min_thr_num; i++)
{
//启动任务线程
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
printf("start thread 0x%x...\n", pool->threads[i]);
}
//启动管理线程
pthread_create(&(pool->adjust_tid), NULL, adjust_thread, (void *)pool);
return pool;
}while();
threadpool_free(pool);
return NULL;
} //把任务添加到队列中
int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg)
{
assert(pool != NULL);
assert(function != NULL);
assert(arg != NULL);
pthread_mutex_lock(&(pool->lock));
//队列满的时候,等待
while ((pool->queue_size == pool->queue_max_size) && (!pool->shutdown))
{
//queue full wait
pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
}
if (pool->shutdown)
{
pthread_mutex_unlock(&(pool->lock));
}
//如下是添加任务到队列,使用循环队列
if (pool->task_queue[pool->queue_rear].arg != NULL)
{
free(pool->task_queue[pool->queue_rear].arg);
pool->task_queue[pool->queue_rear].arg = NULL;
}
pool->task_queue[pool->queue_rear].function = function;
pool->task_queue[pool->queue_rear].arg = arg;
pool->queue_rear = (pool->queue_rear + )%pool->queue_max_size;
pool->queue_size++;
//每次加完任务,发个信号给线程
//若没有线程处于等待状态,此句则无效,但不影响
pthread_cond_signal(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return ;
} //线程执行任务
void *threadpool_thread(void *threadpool)
{
threadpool_t *pool = (threadpool_t *)threadpool;
threadpool_task_t task;
while(true)
{
/* Lock must be taken to wait on conditional variable */
pthread_mutex_lock(&(pool->lock));
//任务队列为空的时候,等待
while ((pool->queue_size == ) && (!pool->shutdown))
{
printf("thread 0x%x is waiting\n", pthread_self());
pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
//被唤醒后,判断是否是要退出的线程
if (pool->wait_exit_thr_num > )
{
pool->wait_exit_thr_num--;
if (pool->live_thr_num > pool->min_thr_num)
{
printf("thread 0x%x is exiting\n", pthread_self());
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
}
}
if (pool->shutdown)
{
pthread_mutex_unlock(&(pool->lock));
printf("thread 0x%x is exiting\n", pthread_self());
pthread_exit(NULL);
}
//get a task from queue
task.function = pool->task_queue[pool->queue_front].function;
task.arg = pool->task_queue[pool->queue_front].arg;
pool->queue_front = (pool->queue_front + )%pool->queue_max_size;
pool->queue_size--;
//now queue must be not full
pthread_cond_broadcast(&(pool->queue_not_full));
pthread_mutex_unlock(&(pool->lock));
// Get to work
printf("thread 0x%x start working\n", pthread_self());
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num++;
pthread_mutex_unlock(&(pool->thread_counter));
(*(task.function))(task.arg);
// task run over
printf("thread 0x%x end working\n", pthread_self());
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num--;
pthread_mutex_unlock(&(pool->thread_counter));
}
pthread_exit(NULL);
return (NULL);
} //管理线程
void *adjust_thread(void *threadpool)
{
threadpool_t *pool = (threadpool_t *)threadpool;
while (!pool->shutdown)
{
sleep(DEFAULT_TIME);
pthread_mutex_lock(&(pool->lock));
int queue_size = pool->queue_size;
int live_thr_num = pool->live_thr_num;
pthread_mutex_unlock(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
int busy_thr_num = pool->busy_thr_num;
pthread_mutex_unlock(&(pool->thread_counter));
//任务多线程少,增加线程
if (queue_size >= MIN_WAIT_TASK_NUM
&& live_thr_num < pool->max_thr_num)
{
//need add thread
pthread_mutex_lock(&(pool->lock));
int add = ;
for (int i = ; i < pool->max_thr_num && add < DEFAULT_THREAD_VARY
&& pool->live_thr_num < pool->max_thr_num; i++)
{
if (pool->threads[i] == || !is_thread_alive(pool->threads[i]))
{
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
add++;
pool->live_thr_num++;
}
}
pthread_mutex_unlock(&(pool->lock));
}
//任务少线程多,减少线程
if ((busy_thr_num * ) < live_thr_num
&& live_thr_num > pool->min_thr_num)
{
//need del thread
pthread_mutex_lock(&(pool->lock));
pool->wait_exit_thr_num = DEFAULT_THREAD_VARY;
pthread_mutex_unlock(&(pool->lock));
//wake up thread to exit
for (int i = ; i < DEFAULT_THREAD_VARY; i++)
{
pthread_cond_signal(&(pool->queue_not_empty));
}
}
}
return NULL;
} int threadpool_destroy(threadpool_t *pool)
{
if (pool == NULL)
{
return -;
}
pool->shutdown = true;
//adjust_tid exit first
pthread_join(pool->adjust_tid, NULL);
// wake up the waiting thread
pthread_cond_broadcast(&(pool->queue_not_empty));
for (int i = ; i < pool->min_thr_num; i++)
{
pthread_join(pool->threads[i], NULL);
}
threadpool_free(pool);
return ;
} int threadpool_free(threadpool_t *pool)
{
if (pool == NULL)
{
return -;
}
if (pool->task_queue)
{
free(pool->task_queue);
}
if (pool->threads)
{
free(pool->threads);
pthread_mutex_lock(&(pool->lock));
pthread_mutex_destroy(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
pthread_mutex_destroy(&(pool->thread_counter));
pthread_cond_destroy(&(pool->queue_not_empty));
pthread_cond_destroy(&(pool->queue_not_full));
}
free(pool);
pool = NULL;
return ;
} int threadpool_all_threadnum(threadpool_t *pool)
{
int all_threadnum = -;
pthread_mutex_lock(&(pool->lock));
all_threadnum = pool->live_thr_num;
pthread_mutex_unlock(&(pool->lock));
return all_threadnum;
} int threadpool_busy_threadnum(threadpool_t *pool)
{
int busy_threadnum = -;
pthread_mutex_lock(&(pool->thread_counter));
busy_threadnum = pool->busy_thr_num;
pthread_mutex_unlock(&(pool->thread_counter));
return busy_threadnum;
} bool is_thread_alive(pthread_t tid)
{
int kill_rc = pthread_kill(tid, );
if (kill_rc == ESRCH)
{
return false;
}
return true;
} //for test
void *process(void *arg)
{
printf("thread 0x%x working on task %d\n ",pthread_self(),*(int *)arg);
sleep();
printf("task %d is end\n",*(int *)arg);
return NULL;
} int main()
{
threadpool_t *thp = threadpool_create(,,);
printf("pool inited"); int *num = (int *)malloc(sizeof(int)*);
for (int i=;i<;i++)
{
num[i]=i;
printf("add task %d\n",i);
threadpool_add(thp,process,(void*)&num[i]);
}
sleep();
threadpool_destroy(thp);
return ;
}
linux 条件变量与线程池的更多相关文章
- Python学习---线程锁/信号量/条件变量同步/线程池1221
线程锁 问题现象: 多线程情况下,CPU遇到阻塞会进行线程的切换,所以导致执行了tmp-=1的值还未赋值给num=tmp,另一个线程2又开始了tmp -=1,所以导致最后的值重复赋值给了num,所以出 ...
- 理解 Linux 条件变量
理解 Linux 条件变量 1 简介 当多个线程之间因为存在某种依赖关系,导致只有当某个条件存在时,才可以执行某个线程,此时条件变量(pthread_cond_t)可以派上用场.比如: 例1: 当系统 ...
- linux条件变量
条件变量用于线程之间的通信,和互斥锁一起使用.条件变量用于及时通知等待的线程条件的变化,使线程不至于错过变化. 考虑下面的情况,有AB两个线程对index这个全局变量进行++,一个线程C用于判断,in ...
- conditon_variable(条件变量)用于线程间同步
conditon_variable(条件变量)用于线程间同步 condition_variable有5个函数,函数名及对应的功能如下: wait阻塞自己,等待唤醒 wait_for阻塞自己,等待唤醒, ...
- 四十二、Linux 线程——线程同步之条件变量之线程状态转换
42.1 线程状态转换 42.1.1 状态转换图 42.1.2 一个线程计算,多个线程获取的案例 #include <stdio.h> #include <stdlib.h> ...
- 在Linux下写一个线程池以及线程池的一些用法和注意点
-->线程池介绍(大部分来自网络) 在这个部分,详细的介绍一下线程池的作用以及它的技术背景以及他提供的一些服务等.大部分内容来自我日常生活中在网络中学习到的一些概念性的东西. -->代码 ...
- linux通过c++实现线程池类
目录 线程池的实现 线程池已基于C++11重写 : 基于C++11实现线程池的工作原理 前言 线程池的概念 使用原因及适用场合 线程池的实现原理 程序测试 线程池的实现 线程池已基于C++11重写 : ...
- linux 条件变量
互斥量就是一把锁,在访问数据时能保证同一时间内只有一个线程访问数据,在访问完以后再释放互斥量上的锁. 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条 ...
- linux条件变量使用和与信号量的区别
近来在项目中用到条件变量和信号量做同步时,这一块一直都有了解,但也一直没有总结,这次总结一下,给大家提供点参考,也给自己留点纪念. 首先,关于信号量和条件变量的概念可以自行查看APUE,我这直接把AP ...
随机推荐
- 【第十三课】监控Linux系统状态
目录 1.查看系统负载命令:w.uptime 2.vmstat详解 3.top动态查看负载 4.sar命令(监控网卡流量) 5.nload命令(监控网卡流量) 6.iostat iotop(监控IO性 ...
- post请求参数Json字符串包含数组的校验和处理
传入参数类型 {"aaa":"aaaa","bbb":"bbb","ccc":"ccc&q ...
- JDBC详解系列(一)之流程
---[来自我的CSDN博客](http://blog.csdn.net/weixin_37139197/article/details/78838091)--- JDBC概述 使用JDBC也挺长 ...
- 浅谈String模块ascii_letters和digits
本文介绍string模块ascii_letters和digits方法,其中ascii_letters是生成所有字母,从a-z和A-Z,digits是生成所有数字0-9. 示例如下: In [2]: c ...
- github添加ssh连接用户
最近打算用flask写一个自己的博客网站,打算把代码放在GitHub上,使用ssh访问.记录下GitHub配置ssh用户的流程. 1.在本地电脑或云服务器上生成ssh公钥和私钥,window下可以进入 ...
- 如何自出版一本书:定制 bookdown
目录 如何自出版一本书:定制 bookdown bookdown 的第一步 亚马逊 Kindle 格式 创建书籍 _bookdown.yml 注意行宽 写在每个 .Rmd 文件的开头 index.Rm ...
- DRF01
1.web应用模式 在web开发中有两种应用模式: 1)前后端不分离 2)前后端分离 2.api接口 为了在团队内部形成共识.防止个人习惯差异引起的混乱,我们需要找到一种大家都觉得很好的接口实现规范, ...
- Daily Scrumming* 2015.12.16(Day 8)
一.团队scrum meeting照片 二.成员工作总结 姓名 任务ID 迁入记录 江昊 任务1036 https://github.com/buaaclubs-team/temp-front/com ...
- Linux内核分析作业四
扒开系统调用的三层皮 一.用户态.内核态和中断 一般现代CPU都有几种不同的指令级别 在高级别执行级别下,代码可以执行特权指令,访问任意的物理地址,称之为内核态 在相应的低指令执行级别下,代码的掌控范 ...
- Mininet安装
Mininet 安装 根据SDNLAB上的实验进行安装.连接地址 需要注意的是切换到用户目录下进行clone github上的源码. 1.卸载之前安装的Mininet 最好是先到目录下看是否有这些文件 ...