Skip to content

Commit

Permalink
add blog (#617)
Browse files Browse the repository at this point in the history
  • Loading branch information
ekkure authored Nov 18, 2024
1 parent 0e33145 commit d678ab9
Show file tree
Hide file tree
Showing 2 changed files with 332 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---

title: rust体会
date: 2024-11-11
categories:
- rust language
tags:
- author:ekkure
- repo:https://github.com/ekkure/blog
---
### Rust编程技巧与示例:宏、算法与类型转换

在Rust编程中,有许多细节和技巧可以帮助开发者更好地组织代码、优化算法性能,以及确保类型安全。本篇博客汇总了一些Rust编程的核心要点和实用代码示例,涵盖了宏的使用、排序算法、树和图的操作等内容。

---

### 1. 宏与#[macro_export]、#[macro_use]

Rust中的宏非常强大,用于生成重复代码和提升代码的灵活性。使用`#[macro_export]`可以导出宏,使其在其他模块或包中可用;而`#[macro_use]`则在现代Rust中被推荐通过`use`语句显式引入。

示例宏定义:
```rust
#[rustfmt::skip]
macro_rules! my_macro {
() => { println!("Check out my macro!"); };
($val:expr) => { println!("Look at this other macro: {}", $val); };
}
```

这里的`#[rustfmt::skip]`用于避免自动格式化,保持代码样式的灵活性和可读性。

---

### 2. Rust中的类型与特性

在实现数据结构或算法时,我们通常需要对泛型类型T施加一些特性约束,例如:
- `Ord`:使得元素可以比较大小,适用于排序、合并等操作。
- `Clone`:便于复制元素值,即使是复杂类型,也可以无所有权转移地复制。
- `Display`:实现字符串友好的格式化输出,便于打印和日志记录。

这些特性可以通过`where`语句在泛型实现中指定:
```rust
impl<T> LinkedList<T>
where T: Ord + Clone + Display
```

---

### 3. 内存操作与指针

Rust通过`unsafe`块支持手动管理内存和指针操作,用于高性能或底层操作。
例如,获取节点的指针并解引用:
```rust
let node_ptr = Some(unsafe { NonNull::new_unchecked(Box::into_raw(node)) });
res.add((*node_ptr.as_ptr()).val.clone());
cur_a = (*node_ptr.as_ptr()).next; // 注意这里直接获取的是ta的next指针
```

指针的安全解包和操作要格外小心,可以使用`Option`配合`unsafe`避免空指针风险。

---

### 4. 算法设计示例

#### 4.1 链表与树的操作

##### 插入与查找
在链表或树结构中,我们经常用到`Option`类型来表示节点的存在与否。例如,在插入和查找二叉树中,可以选择使用`if let`语句来处理`Some``None`的情况:
```rust
fn insert(&mut self, value: T) {
if let Some(ref mut node) = self.root {
node.insert(value);
} else {
self.root = Some(Box::new(TreeNode::new(value)));
}
}
```
这种写法在处理可变引用时尤其简洁。

#### 4.2 排序算法与Ord与PartialOrd

选择排序等算法需要比较泛型元素的大小,通常需要`PartialOrd`特性来支持部分排序(如非全序关系的情况),而对于要求全序的场景可以使用`Ord`

#### 4.3 深度优先与广度优先搜索

在图算法中,深度优先搜索(DFS)和广度优先搜索(BFS)是两种基础的遍历方式:
- DFS示例:
```rust
fn dfs_util(&self, v: usize, visited: &mut HashSet<usize>, visit_order: &mut Vec<usize>) {
visited.insert(v);
visit_order.push(v);
for &nei in self.adj[v].iter() {
if !visited.contains(&nei) {
self.dfs_util(nei, visited, visit_order);
}
}
}
```

- BFS示例:
```rust
fn bfs_with_return(&self, start: usize) -> Vec<usize> {
let mut visit_order = vec![];
let mut visited = vec![false; self.adj.len()];
let mut queue = VecDeque::new();
queue.push_back(start);
visited[start] = true;

while let Some(node) = queue.pop_front() {
visit_order.push(node);
for &neighbor in &self.adj[node] {
if !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
}
visit_order
}
```

#### 4.4 平衡堆的插入与调整

Rust标准库中`Vec`的`swap_remove`方法可以高效地删除指定位置的元素,适用于实现优先队列等堆结构:
```rust
let result = self.items.swap_remove(1); // 移除并返回指定位置的元素
```

在删除元素后,可以通过调整堆结构(如最小/最大堆)来保持堆的性质。

---

### 5. 实现栈与队列

使用双队列实现栈的操作逻辑:
```rust
pub struct myStack<T> {
q1: Queue<T>,
q2: Queue<T>
}

impl<T> myStack<T> {
pub fn push(&mut self, elem: T) {
self.q2.enqueue(elem);
while !self.q1.is_empty() {
self.q2.enqueue(self.q1.dequeue().unwrap());
}
std::mem::swap(&mut self.q1, &mut self.q2);
}
}
```

这种方法利用队列的FIFO特性来模拟栈的LIFO特性。

---

### 6. 函数与内存管理

Rust中的`Box``unsafe`结合用于手动管理堆内存。`Box::from_raw`可以从裸指针重新创建`Box`,这在需要手动内存管理的场景中非常有用。
```rust
unsafe fn raw_pointer_to_box(ptr: *mut Foo) -> Box<Foo> {
let mut ret: Box<Foo> = unsafe { Box::from_raw(ptr) };
ret.b = Some(String::from("hello"));
ret
}
```

这种方法常用于FFI(外部函数接口)中将指针恢复为拥有所有权的Rust类型。

---

### 总结

Rust语言通过丰富的内存管理工具和类型系统,确保了在安全性和性能上的平衡。无论是自定义数据结构还是排序、图遍历等基础算法,Rust的特性可以为代码提供极大的灵活性和安全保障。
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---

title: 调度与死锁
date: 2024-11-11
categories:
- os
tags:
- author:ekkure
- repo:https://github.com/ekkure/blog

---
## 调度

#### **三级调度:**

作业调度、高级调度(频次最低):主要解决:接纳多少个任务+接纳那哪些任务这两个工作

进程调度、低级调度(频次最高): 必须有、**核心****确定哪个进程可以占有CPU并执行**

中级调度:将那些暂时不能运行的进程从内存挂起到外存,(阻塞状态下进程实体(程序 + 数据 + PCB)还在内存中,而挂起状态会把进程实体挂到外存,但是PCB会存在系统内核空间中,会记录进程在外存的状态以及位置),一般在**内存紧张**时使用



高级调度,用于批处理系统中,将任务从外存调度到内存中去。(在分时/实时系统中,任务是直接在内存,因此没有高级调度)

分时系统:**只有进程调度**

批处理系统:**进程调度 + 作业调度**



#### 调度算法相关

准则:周转时间(常用于**批处理系统**)、平均周转时间、带权周转时间

响应时间(交互性作业、分时系统)、截止时间的保证(实时系统)、优先权准则

周转时间 = 完成时间 - 到达时间

带权周转时间 = 周转时间 / 服务时间


**调度算法**

- FCFS(first come first serve), SJ(P)F等等
对于抢占式调度,注意**服务时间的更新,然后再比较,看谁抢**

- 高优先权 优先调度算法

静态优先权:简单,但存在饥饿现象

动态优先权:eg Rp = (等待时间 + 服务时间)/ 服务时间 作为优先权 1 + tw / ts;

- 时间片轮转 ......?

多级反馈队列 S1 < S2 < S3 优先权 S1 > S2 > S3



- 实时调度

非抢占:轮转 || 优先权

抢占:基于中断时钟,好处是减少了上下文保存切换的次数

​ 立即抢占

实时调度算法:EDF、LLF,还有例题

- 其他一些??

MPS:CPU共享内存, 共享缓存(单个儿独立的,容易出现绑定,忙闲不均)

SMP中进程分配方式:静态分配和动态分配

​ 调度方式: 自调度和成组调度(两种方式就对应了用户级线程和系统级线程), 专用处理机分配?


## 死锁

**一些定义**

- 可剥夺资源:如主存,CPU,可以在使用时被强占的资源
- 不可剥夺资源:不可被打断抢占的资源,如驱动器,打印机
- 永久资源(外存),临时资源(进程运行过程中临时产生的数据资源等等)

**竞争非剥夺资源,或者竞争临时资源可导致死锁**

### 死锁的必要条件

- 互斥条件:进程互斥的使用临界资源
- 不剥夺条件(不可抢占)
- 请求-保持条件:进程在申请新的资源的同时,保持对某些资源的占有
- 环路等待:循环等待链





### 解决死锁的方法

从严格依次降低,为

预防 -> 避免 -> 检测与解除

#### 预防

上面4个条件是死锁的必要条件 , Deadlock -> 4 其逆否命题为 !4 -> !Deadlock,所以我们从4个条件入手

1. 互斥,并没有好的办法
2. 不抢占:不抢占变成"抢占",如果进程申请不到全部资源时,主动释放
3. 请求保持条件:使用AND机制,但是有点浪费资源
4. 环路等待:破除环路,资源排序,参考哲学家进餐

#### 避免死锁

**这是比较中庸的做法,既不损耗很多的效率,也比较的严格**

##### 银行家算法

一种是,资源分配表,为

| Process | Allocation | Need | Available |
| ------- | ---------- | ---- | --------- |
| | | | |

另一种是,计算表

| Work | Need | Allocation | work + Allocation | Finish |
| ---- | ---- | ---------- | ----------------- | ------ |
| | | | | |

**对资源进行分配时,分成两步**

1. 判断分配请求 R是否满足 R < Available && R < Need
2. 如果满足1,使用表1表示分配后的资源表T1,再次计算是否存在安全序列,如果不安全,退回至T0,否则保存T1,下次分配将从T1开始。

#### 检测和解除

使用方法:**资源分配图**

**几个结论**

- 不可完全简化 => 存在死锁
- 分配图中无环 => 不会存在死锁
- 分配图中有环 => 不一定死锁

简化方法,对一个资源分配图,首先考虑持有边,如果持有者线程能够完成(获得所有需要的资源),将持有边消去后,将资源返回,如果不能完成,边消去后,仍保持资源占有,直到完成。

然后考虑请求边,如果请求的资源有空闲的,可以把边消去,若请求线程能够完成,则可将该资源返回,否则保持占有

重复上述过程,直至卡住,或者全部成孤立。

**解除**

通过撤销进程或者挂起进程来释放一些资源,进而推动僵持状态。

而具体的对哪些进程,以什么样的顺序进行操作,可以参考`Dijkstra`之类的算法,找到一种损耗最小、利益最大的方法。

0 comments on commit d678ab9

Please sign in to comment.