[{"content":"0. 理论 这一部分给出常见算法的模板及其性质（复杂度，稳定性 etc.).\n0.1 快速排序 快排的思想是divide and conquer, 通过一个Pivot元素将数组划分为两半，其中前一半小于pivot，后一半大于Pivot。然后在每个子数组中再执行相同步骤直到子数组不可再划分。\n最坏时间复杂度：O(n^2), 最好/平均时间复杂度：O(NlogN) 稳定性：不稳定\n给一个模板\nvoid quick_sort(vector\u0026lt;int\u0026gt; \u0026amp;nums, int l, int r) { if (l + 1 \u0026gt;= r) { return; } int first = l, last = r - 1, key = nums[first]; while (first \u0026lt; last){ while(first \u0026lt; last \u0026amp;\u0026amp; nums[last] \u0026gt;= key) { --last; } nums[first] = nums[last]; while (first \u0026lt; last \u0026amp;\u0026amp; nums[first] \u0026lt;= key) { ++first; } nums[last] = nums[first]; } nums[first] = key; quick_sort(nums, l, first); quick_sort(nums, first + 1, r); } quick_sort(nums,0,nums.size()) ;//调用方法 0.2 归并排序 归并排序的思想也是divide and conquer, 通过不断地将有序的子数组合并为更大的有序数组从而得到结果。\n最坏/最佳复杂度：O(NlogN) 稳定性：稳定\n注意一般来说我们写的都不是in place merge sort, 即一般都会传给函数一个额外的空间用于作数组合并，但也有in place的版本，在此不作赘述。\n给一个模板\nvoid merge_sort(vector\u0026lt;int\u0026gt; \u0026amp;nums, int l, int r, vector\u0026lt;int\u0026gt; \u0026amp;temp) { if (l + 1 \u0026gt;= r) { return; } // divide int m = l + (r - l) / 2; merge_sort(nums, l, m, temp); merge_sort(nums, m, r, temp); // conquer int p = l, q = m, i = l; while (p \u0026lt; m || q \u0026lt; r) { if (q \u0026gt;= r || (p \u0026lt; m \u0026amp;\u0026amp; nums[p] \u0026lt;= nums[q])) { temp[i++] = nums[p++]; } else { temp[i++] = nums[q++]; } } for (i = l; i \u0026lt; r; ++i) { nums[i] = temp[i]; } } merge_sort(nums,0,nums.size(),temp);// 调用方法 0.3 插入排序 插入排序很简单，但时间久了忘记了，写一下吧。\n插入排序的思想是使得有序的数组长度逐渐扩大，假设我们起初认为只有第一个元素是有序的，那么遍历到第二个元素时，检查第二个元素和第一个元素的大小关系，若第二个元素更大则将其插到第一个元素的前面，这样，我们就得到长度为2的有序序列了，以此类推，不断地将第n个元素插入到长度为n-1地有序数组中, 直到n=len(nums)。\n最坏/平均时间复杂度: O(N^2) 最好时间复杂度: O(N) 稳定性: 稳定\n","permalink":"https://Moondok.github.io/blogs/posts/tech/algorithm_notes_sort/","summary":"常见排序算法的模板和性质、一些leetcode习题","title":"leetcode刷题笔记（二） 排序篇"},{"content":"0. 理论 贪心算法的基本要素包括两个部分，分别是：\n贪心选择性质：所求问题的整体最优解可以通过一系列的局部最优解得到 最优子结构性质：一个问题的最优解包含其子问题的最优解 note that 动态规划只包含第二个性质，因此我们需要把每个子问题的最优解都存储起来。\n1. 实践 1.1 Non-overlapping Intervals - LeetCode 435 [link] 解析：这个题是贪心算法最典型的问题——活动安排问题换了一个问法。题目既然最少要求删去多少个interval才能使得剩余的interval不重叠，那我们只需要求最多能排列几个不重叠的interval即可。因此我们的做法应该是：我们按照所有间隔的终止时间进行排序，然后从头到尾进行遍历，如果后一个间隔的开始时间大于等于前一个的终止时间，便可以排下，否则舍弃掉后一个间隔。\n上代码：\nclass Solution { public: static bool cmp(const std::vector\u0026lt;int\u0026gt; \u0026amp; a,const std::vector\u0026lt;int\u0026gt; \u0026amp;b) { return a.at(1)\u0026lt;b.at(1); } int eraseOverlapIntervals(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; intervals) { std::sort(intervals.begin(),intervals.end(),cmp); int n = intervals.size(); int n_compatible = 0; int pre_end_time = -50001; //题目中说最早的开始时间是-50000 for(auto \u0026amp; interval : intervals) { if(interval.at(0)\u0026gt;=pre_end_time) { n_compatible++; pre_end_time = interval.at(1); } } return n - n_compatible; } }; 1.2 Minimum Number of Arrows to Burst Balloons - LeetCode 452 [link] 解析 上一个题目是为了让不同的区间尽可能错开，这个题目则反其道行之，旨在让不同的区间尽可能相交，试想如果我们能让一支箭正好射中多个气球的交接处，那样便可以用最少的箭射爆所有气球。因此我们的做法是：按照气球的左端点从小到大排序，遍历得到的序列，在遍历的时候维护一个值x，该值代表上一个箭射击的位置，这个值同时也是其所能射爆的所有气球中右端点最小的一个。在遍历过程中，如果某个气球其左端点比x要大，那么说明上一只箭无法覆盖当前气球，只能多花一只箭；否则说明上一只箭头可以射中当前这只气球，不需要新增箭，这时需要注意，我们需要将x的值更新为新气球的左端点和x中更小的一个。这是因为，如果上一只箭覆盖的气球是[1,6] [2,7],如果新的气球是[2,5] 的话，这只箭最远只能射在5的位置才能保证射爆3只气球\n上代码\nclass Solution { public: static bool cmp(const std::vector\u0026lt;int\u0026gt; \u0026amp; a, const std::vector\u0026lt;int\u0026gt; \u0026amp; b) { return a.at(0)\u0026lt;b.at(0); } int findMinArrowShots(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; points) { std::sort(points.begin(),points.end(),cmp); int min_right_bound= points.at(0).at(1); //第一个区间的右侧边界作为起点 int ans=1; for(int i=0;i\u0026lt;points.size();i++) { if(points.at(i).at(0)\u0026lt;=min_right_bound)//要是后面的点的左边界比当前点的右边界小，那就可以共享一支箭头 { if(points.at(i).at(1)\u0026lt;min_right_bound) min_right_bound=points.at(i).at(1); continue; } else { ans++; min_right_bound=points.at(i).at(1); } } return ans; } }; 1.3 Partition Labels - LeetCode 763 [link] 解析 这一题和上一题极为相似但在细节上又有微妙的不同。要求我们划分尽可能多的区间，但需要保证对于每一个字母，该字母只能在一个区间内出现。因此对于每个单词我们维护一个hash table, 记录其第一次出现的位置和最后一次出现的位置，在我们最后的结果中，这个区间必定是其中一个区间的子集。而如果有两个字母的存在区间但凡有一点重叠，我们就必须将其扩充成一个新的大区间。因此我们的做法是：将字母按照第一次出现的位置进行排列，从左到右遍历，如果新出现的字母的第一次出现的位置大于当前维护区间的右端点，我们就开一个新的区间；否则，我们就把这个区间合并在原有区间里面。这里需要注意的是，新区间的右端点是要取一个最大值（这里和上一题进行对比，上一题在更新箭的位置时是取最小值因为要保证一支箭可以射中所有气球；这里取较大值是为了保证当前的区间可以覆盖当前的字母，比如前一个字母的区间是[1,5] , 后一个是[2, 6], 那么至少得需要一个[1,6] 的区间。\n上代码\nstruct info { char ch; int first_pos=-1; int last_pos=0; bool operator\u0026lt;(const info \u0026amp; a)const { return this-\u0026gt;first_pos\u0026lt;a.first_pos; } }; class Solution { public: vector\u0026lt;int\u0026gt; partitionLabels(string s) { //std::map\u0026lt;int,info\u0026gt; letter2info; std::vector\u0026lt;info\u0026gt; letter2info(26); //只有26个字母，用vector作hash好了 for(int i=0;i\u0026lt;26;i++) letter2info.at(i).ch='a'+i; for (int i=0;i\u0026lt;s.size();i++) { if(letter2info.at(s.at(i)-'a').first_pos==-1) letter2info.at(s.at(i)-'a').first_pos=i; letter2info.at(s.at(i)-'a').last_pos=i; } std::sort(letter2info.begin(),letter2info.end()); std::vector\u0026lt;int\u0026gt; ans; int start; for(int i=0;i\u0026lt;26;i++) { if(letter2info.at(i).first_pos!=-1) { start=i; break; } } int right_bound= letter2info.at(start).last_pos; int left_bound=letter2info.at(start).first_pos; for( int i=start+1; i\u0026lt;26 ;i++) { if(letter2info.at(i).first_pos\u0026lt;=right_bound) right_bound=std::max(right_bound,letter2info.at(i).last_pos) else //新的区间 { ans.push_back(right_bound-left_bound+1); left_bound=letter2info.at(i).first_pos; right_bound=letter2info.at(i).last_pos; } } ans.push_back(right_bound-left_bound+1); return ans; } }; 1.4 最小生成树 最小生成树的定义在此不再赘述，在连通图中构建最小生成树的方法主要包括Prim算法和Kruskal 算法。这两种算法都基于MST性质。\nMST性质 ： 假设$G=(V,E)$是连通带权图，U是V的真子集。如果$(u,v)\\in E$, 且$u\\in U$，$v\\in V-U$，且在所有这样的边中，(u,v)的权$c[u][v]$最小，那么一定存在G的一棵最小生成树，它以$(u,v)$为其中一条边。\nMST性质的证明略。\nPrim算法和Kruskal算法分别着眼于最小生成树的节点和最小生成树的边。\n1.4.1 Prim算法 在prim算法中，我们首先将图中的所有节点拆分为两个集合$s1$和$s2=V-s1$，其中第一个集合最初包含第0个节点（方便起见）。\n随后，我们循环 $n-1$ （n是节点数目）次，在每次循环中，我们取连接$s1$中节点和$s2$中节点的权值最小的一条边$(u,v)$，将$s2$ 中的节点$v$ 取出放入$s1$ 中，直到$s1$ 中包含所有节点，而我们每次取出的节点构成了最小生成树。\n我们以这个题目为例 leetcode 1584\n在这里面我们创建了两个数组：$closest$和 $lowest$ ，其中$closest$ 中存储的是$s2$ 中的节点连接 $s1$ 中节点的所有的边中权重最短的一条边在$s1$ 中的连接点；$lowest$ 中存储的是$s2$ 中的节点连接 $s1$ 中节点的所有的边中权重最短边的权重，即有 $$ lowest[j]=map[closest[j]][j] $$ 另外一个数组$is_visited$ 用来表示节点在$s1$ 集合中还是在$V-s1$ 集合中。\n需要说明的是本题中$closest$ 存在感不强，主要是因为题目只要最小的权重，没要求具体的树的信息。\n代码如下\nclass Solution { public: int distance(std::vector\u0026lt;int\u0026gt; \u0026amp; p1, std::vector\u0026lt;int\u0026gt; \u0026amp; p2) { return abs(p1.at(0)-p2.at(0))+abs(p1.at(1)-p2.at(1)); } int minCostConnectPoints(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; points) { int map[1000][1000]; for(int i=0;i\u0026lt;points.size();i++) for(int j=i;j\u0026lt;points.size();j++) if(j==i) map[i][j]=0; else map[i][j]=map[j][i]=distance(points.at(i),points.at(j)); int is_visited [1000]; for(int i=0;i\u0026lt;points.size();i++) is_visited[i]=0; is_visited[0]=1; int closest[1000]; //代表V-s1里面的点到s1距离最小的那个连接点 for(int i=0;i\u0026lt;points.size();i++) closest[i]=0; int lowest[1000]; // 代表V-s1里面的点到s1最小的距离 for(int i=0;i\u0026lt;points.size();i++) lowest[i]=map[i][0]; int ans=0; int max_value=1e7; int min_len; int min_idx; for(int i=0;i\u0026lt;points.size()-1;i++) { min_len=max_value; min_idx=0; for(int j=0;j\u0026lt;points.size();j++) { if(is_visited[j]==0\u0026amp;\u0026amp; lowest[j]\u0026lt;min_len) { min_idx=j; min_len=lowest[j]; } } is_visited[min_idx]=1; ans+=min_len; //更新lowest for(int i=1;i\u0026lt;points.size();i++) { if(!is_visited[i] \u0026amp;\u0026amp; map[min_idx][i]\u0026lt;lowest[i]) { closest[i]=min_idx; lowest[i]= map[min_idx][i]; } } } return ans; } }; 1.4.2 Kruskal 算法 Kruskal算法是对于边来进行的，起初，该算法将所有节点看作孤立的分支，然后按照权重从小到大的顺序遍历每一条边，如果发现这条边的两个节点不在一个分支中，就把他们归到一个分支里面；如果当前的边对应的两个顶点已经属于一个连通分支了，则跳过该边，直到所有节点都被放到一个连通分支为止。\n这里需要说明的是，在判断两个节点是否属于一个分支的过程中，我们使用了简单的并查集。\n代码如下，还是上面那个题 leetcode 1584\nclass Solution { public: struct edge { int id1=0; int id2=1; int len=0; bool operator\u0026lt;(const edge a) const { return this-\u0026gt;len\u0026lt;a.len; } }; int distance(std::vector\u0026lt;int\u0026gt; \u0026amp; p1, std::vector\u0026lt;int\u0026gt; \u0026amp; p2) { return abs(p1.at(0)-p2.at(0))+abs(p1.at(1)-p2.at(1)); } int find(int i,const std::vector\u0026lt;int\u0026gt; \u0026amp; fathers) { if(fathers.at(i)==i) return i; else return find(fathers.at(i),fathers); } bool union_ (int i,int j, std::vector\u0026lt;int\u0026gt; \u0026amp; fathers) { int x=find(i,fathers); int y=find(j,fathers); if(x==y) return false; else { fathers.at(x)=y; return true; } } int minCostConnectPoints(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; points) { if(points.size()==1) return 0; std::vector\u0026lt;edge\u0026gt; edges; for(int i=0;i\u0026lt;points.size();i++) { for(int j=i+1;j\u0026lt;points.size();j++) { if(i!=j) { edge new_edge; new_edge.id1=i; new_edge.id2=j; new_edge.len=distance(points.at(i),points.at(j)); edges.push_back(new_edge); } } } std::sort(edges.begin(),edges.end()); std::vector\u0026lt;int\u0026gt; fathers=std::vector\u0026lt;int\u0026gt;(1000); for(int i=0; i\u0026lt;points.size() ;i++) fathers.at(i)=i; int ans=0; int k=0; for(auto \u0026amp; edge :edges) { int i=edge.id1; int j=edge.id2; if(union_(i,j,fathers)) { ans+=edge.len; k++; } if(k\u0026gt;=points.size()) break; } return ans; } }; 1.5 Best Time to Buy and Sell Stock II - LeetCode 122 link 解析 这道题是相当经典的贪心了，对于给定一个时间序列内的股价且不限交易次数，能得到最多利润的办法就是在local minimum处买入，在买入后的第一个local maximum处卖出。这一方法的贪心性质也很好证明：假设在local maximum的某个时间后股价更高的时间点卖出可以得到更多的利润。那么我们设买入的价格是 v1, local maximum是v2 , v2后一个时间戳的股价为v3, v3后某个股价高于v2的值为v4, 容易得到 (v2-v1)+(v4-v3) \u0026gt; v4-v1 (因为v3比v2小)。\n代码如下\nclass Solution { public: int maxProfit(vector\u0026lt;int\u0026gt;\u0026amp; prices) { int min_price=prices.at(0); int max_price=min_price; int profit=0; for(int i=1;i\u0026lt;prices.size();i++) { if(prices.at(i)\u0026lt;max_price) //一旦在max price后面出现更小的值，说明max value是local maximum { profit+=max_price-min_price; min_price=max_price=prices.at(i); } else { min_price=std::min(min_price,prices.at(i)); max_price=std::max(max_price,prices.at(i)); } } profit+=max_price-min_price; return profit; } }; 1.6 Queue Reconstruction by Height - LeetCode 406 link 解析 这个题看题干有点晦涩，不如直接看例子。由于每个tuple的第二个元素代表的是在它之前且比它大的元素，我们很容易想到我们先按照大小排序，然后对于每个tuple, 将其向后挪动x个位置即可（x为第二个元素的值）。由于前一个tuple的挪动会干扰到后一个元素（考虑[(1,1)(2,1)(3,0)], 正确的结果应该是[(3,0),(1,1),(2,1)], 但由于(1,1)先一步向后挪动了一个位置，导致后挪动的(2,1) 的移动操作仅仅使得列表恢复到原来的状态），我们逆序遍历排好序的列表并执行上述操作。\n代码如下\nclass Solution { public: static bool cmp(const std::vector\u0026lt;int\u0026gt; \u0026amp; a, const std::vector\u0026lt;int\u0026gt; \u0026amp; b ) { if (a.at(0)\u0026lt;b.at(0)) return true; else if (a.at(0)==b.at(0) \u0026amp;\u0026amp; a.at(1)\u0026lt;b.at(1) ) return true; return false; } vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; reconstructQueue(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; people) { std::sort(people.begin(),people.end(),cmp); for (int i=people.size()-1; i\u0026gt;=0 ;i--) { int num_geq=people.at(i).at(1); int current_height= people.at(i).at(0); for(int j=i-1;j\u0026gt;=0;j--) { if(people.at(j).at(0)==current_height) num_geq--; if(num_geq==0) break; } //geq 是多少，説明當前這個元素需要和後面的多少位調換順序 auto target_pos=people.begin()+i+num_geq+1; people.insert((target_pos),people.at(i)); people.erase(people.begin()+i); } return people; } }; 在讨论区有人给出了代码上更简单的解法，在这里也mark一下 ( from leetcode user tonygogogo )\nvector\u0026lt;pair\u0026lt;int, int\u0026gt;\u0026gt; reconstructQueue(vector\u0026lt;pair\u0026lt;int, int\u0026gt;\u0026gt;\u0026amp; people) { sort(people.begin(), people.end(),[](pair\u0026lt;int,int\u0026gt; p1, pair\u0026lt;int,int\u0026gt; p2){ return p1.first \u0026gt; p2.first || (p1.first == p2.first \u0026amp;\u0026amp; p1.second \u0026lt; p2.second); }); vector\u0026lt;pair\u0026lt;int,int\u0026gt;\u0026gt; sol; for (auto person : people){ sol.insert(sol.begin() + person.second, person); } return sol; } ending 做过的所有贪心相关的leetcode题目在这个列表下面，供某日复习使用。\n","permalink":"https://Moondok.github.io/blogs/posts/tech/2024_03_01_algorithm_notes_greedy/","summary":"leetcode上有关贪心算法的一些题目、prim算法、kruskal算法","title":"leetcode刷题笔记（一） 贪心篇"},{"content":"0. 前言 图形学大牛王华民在知乎上面的一篇回答中写到，搞科研分为这么几个阶段，首先是入门阶段，这个阶段需要掌握一些知识和工具，知道一些方向和它们的经典工作；接下来是上手阶段，这个阶段要学习研究的套路，学会知道研究什么问题；然后是灌水阶段和聚焦阶段（这俩阶段离我稍远就不说了）。这个回答写的很好，链接在这里。\n目前对于Neural Rendering这块，我确实读了一些文章了，但涉及到具体实现，也就是上手动或者写一个项目还是有点慌，一方面确实是拿Python几乎没怎么写过大一点的project, 另一方面也是由于之前看code大多更关注模型的定义，而要让这个model跑起来，还需要很多步骤。\n为了改善这一状况，争取更好更早地完成上手阶段，迈入灌水阶段，我决定好好读几个project的code. 这个算是第一篇。\n最近在帮着学长搞3D scene segmentation via Neural Rendering的事情，而这块做的最早的是Davison教授组的一篇semantic_nerf, 我们就从它开始。\nData preparation","permalink":"https://Moondok.github.io/blogs/posts/tech/2024_02_26_starting_engaging_research/","summary":"","title":"我准备开始写一些重要工作的代码解读"},{"content":"0. 说明 该系列小结为我在2023年秋修读同济大学操作系统课程的笔记，我校的操作系统课程主要以Unix v6 为范例进行剖析，因此以下的所有内容只适用于Unix v6 操作系统内核。另外，我校操作系统课程组对于Unix v6 操作系统的原始代码进行了面向对象的重构，下面给出的所有代码均为重构过后的代码片段。\n1. fork 1.1 fork系统调用简介与范例 fork 是一个系统调用，其主要功用为创建一个和当前进程 完全相同 的进程，新创建的进程为当前进程的子进程，二者的可交换部分完全相同，但彼此隔离，互不干扰，代码段共享。fork 的返回值是新创建的子进程的pid。\n下面是一个在用户态下调用fork 系统调用的代码示例片段。\nint a=0; main( ) { int i ; while( ( i=fork( ) ) == -1 ); if(i) { a = a+1; printf(“parent : a = %d\\n”, \u0026amp;a); } else { a=a+4; printf(“child : a = %d\\n”, \u0026amp;a); } } 在上面的代码段中，我们首先通过fork 创建了一个子进程，其pid 存储在i 变量中。在fork 系统调用成功创建新进程并准备返回时（系统调用入口程序SystemCallEntrance函数），系统执行例行调度。此时，新创建的子进程和原进程都可能被调度上台，具体情况由0号进程决定。\n假如原进程被先调度上台，则由于子进程创建成功，pid 势必不为0，进入if 分支，将a 的值改为1 ,并执行打印。在这之后原进程结束，子进程上台，进入else 分支，将a 的值改为4 （注意 ：子进程和父进程的数据段相互隔离，因此子进程中a的值仍为0），并进行打印，应用程序的输出为：\nparent : a=1 child : a=4 假如子进程被先调度上台，则进入else 分支，修改a 的值后打印。子进程结束后父进程被调度上台，进入if 分支后修改a 的值后执行打印，因此应用程序的输出为 :\nchild : a=4 parent : a=1 问题： 看到这里大家想必会有一个疑问，为什么子进程上台之后会进入else 分支执行呢？\n这是一个比较复杂的问题，我们需要读一读代码才能说清楚这件事。\n1.2 fork 系统调用代码解释 用户态下执行fork 系统调用的钩子函数如下\nint fork() { int res; __asm__ __volatile__ ( \u0026quot;int $0x80\u0026quot;:\u0026quot;=a\u0026quot;(res):\u0026quot;a\u0026quot;(2)); // 2#系统调用 if ( res \u0026gt;= 0 ) return res; return -1; } 这段代码很好理解，即使用int 0x80 指令让系统陷入内核后执行2号系统调用fork。\nfork 系统调用的函数如下\nint SystemCall::Sys_Fork() { ProcessManager\u0026amp; procMgr = Kernel::Instance().GetProcessManager(); procMgr.Fork(); return 0; /* GCC likes it ! */ } 可以看到在系统调用函数中，我们获取了内核的ProcessManager 对象，并执行其Fork 方法，接下来我们我们进入Fork函数内部去看看。\nvoid ProcessManager::Fork() { User\u0026amp; u = Kernel::Instance().GetUser(); Process* child = NULL;; /* 为子进程分配空闲的 process 项 */ for ( int i = 0; i \u0026lt; ProcessManager::NPROC; i++ ) { if ( this-\u0026gt;process[i].p_stat == Process::SNULL ) { child = \u0026amp;this-\u0026gt;process[i]; break; } } if ( child == NULL ) { /* 没有空闲 process 表项，返回 */ u.u_error = User::EAGAIN; return; } /* 调用 Newproc( )创建子进程，复制父进程图像 */ L： if ( this-\u0026gt;NewProc() ) { /* 新建子进程被 Swtch( )选中上台运行， *执行 Swtch( )函数逻辑，返回值是 1 *运行于 Newproc( )栈帧，返回地址是 if 语句。*/ u.u_ar0[User::EAX] = 0; // 子进程 fork()系统调用返回 0 u.u_cstime = 0; // 清 0 子进程的时间统计量 u.u_stime = 0; u.u_cutime = 0; u.u_utime = 0; } else { /* 子进程图像创建完毕后，父进程 Newproc( )返回 0 */ u.u_ar0[User::EAX] = child-\u0026gt;p_pid; // 父进程 fork()系统调用的返回值是子进程的 pid } return; } 在这个函数中，我们首先在process 表中查看有无空闲的表项，接着调用NewProc 函数。NewProc 函数的代码段很长，在此就不给出了，其主要功用是将父进程的可交换部分复制给子进程，且将子进程User 结构中的相对表指针u_MemoryDescriptor.m_UserPageTableArray 指向子进程的相对表。在完成了上述工作后，NewProc函数返回0。\n我们先停一下理一理头绪，现在我们的子进程已经创建起来了，其核心栈中栈顶处的几个栈帧分别为NewProc, Fork 和Sys_Fork ，子进程User 结构中的u_srav 中存储的esp和ebp 指针分别指向的是核心栈顶的NewProc栈帧的顶和底。\n当父进程的NewProc 返回时，其返回L 标签处的else 分支执行（NewProc的返回值为0），将新创建的子进程的pid存储在父进程核心栈中EAX 寄存器对应的区域以便我们在系统调用返回时能将返回值（即为子进程pid ）带入EAX 寄存器中。\n接下来Fork 函数返回，Sys_Fork 系统调用返回，在系统调用入口程序下半段执行例行调度Swtch 函数。\n假如此时被调度上台的还是父进程，则父进程通过自己的User 结构中的u_srav 找到Swtch 栈帧，执行Swtch的后半段后将其栈帧撤销，在这之后，返回系统调用入口程序的后半部分执行，回到用户态，非常合理顺畅。\n假如此时被调度上台的是我们新创建的子进程，则同样可以根据子进程的User 结构中的u_srav 找到\u0026hellip; 慢着！子进程核心栈中有Swtch吗，没有啊！子进程是被父进程创建出来的，其从来就没执行过，上哪去找Swtch 栈帧？\n好，知道了这一点，让我们看看接下来会发生什么。尽管子进程中没有Swtch 栈帧，但Swtch 下半段还是要走，我们将Swtch 的返回值 1 熟练地放进EAX 寄存器中，然后撤销核心栈中栈顶的那个栈帧，对应的汇编代码如下\nmov $1 %eax mox %ebp %esp pop %ebp ret 那么此时我们撤销的栈帧是哪个倒霉蛋的呢？没错，就是NewProc 函数的栈帧！（这一点在上面提到过）换句话说，我们把NewProc 的栈帧当作Swtch 栈帧撤销掉了，并把Swtch的返回值1 当作NewProc 的返回值放入了EAX 寄存器中，在这之后返回了Fork函数。\nFork 函数一看，嗯? 返回值是1？好好好，那么进入L 标签的if 标签执行，在这其中将0存储在父进程核心栈中EAX 寄存器对应的区域。接下来Fork返回，Sys_Fork系统调用返回，恢复系统调用现场。用户态程序从EAX 寄存器中拿到fork 的返回值0, 随即进入到else 分支中。\nif(i) #这里拿到的值为0 { ... } else #子进程进入这里执行！ { a=a+4; printf(“child : a = %d\\n”, \u0026amp;a); } 到这里，fork 系统调用大概就说完了，这其中我花了大段的篇幅解释了1.1 小节末尾处的那个问题，略过了很多细节，比如NewProc函数中父进程的可交换部分复制给子进程的过程，以及子进程的User结构中指向相对地址映射表的指针的获取过程等等。这些内容较为细碎，但理解难度不大，因此在此不再赘述。\n","permalink":"https://Moondok.github.io/blogs/posts/tech/os_process_manage_fork_exit_wait/","summary":"","title":"操作系统笔记-进程管理-创建与退出"},{"content":"写在前面 我本来以为我的第一篇博客是一篇正经的技术文档，没想到是出去玩\u0026hellip; Anyway, 鉴于我在上学期（2023年上半年）就看到了浦东美术馆关于这个展览的宣传，到现在只有一个月就闭展了，故决定这周末进一趟城（2023-10-22）。\n相亲角 为什么我先去了相亲角呢，是这样的。\n由于我觉得浦东美术馆不足以逛一天，所以我决定先去上海历史博物馆逛上几个小时；又因为上海历史博物馆就在人民公园正门的旁边，其入口处就是相亲角\u0026hellip; 嗯，很合理对吧。\n相亲角的阿姨爷叔很多，每个人面前摆着一张类似于简历的介绍单，上面大概写着子女的年龄，工作，薪资，家庭条件等等等等。或许是因为疫情，也可能是我上次来得太早了，相比于上次来（21年秋），这次很明显感觉公园门口的来为子女相亲的人变多了。\n根据我的观察，相亲的主力人群（指介绍单上面的子女们，来的大多是家长, or 媒人？）大部分集中于85年-95年这个年龄段，偶有70后和95后。大片大片的985（这里吐槽一句，看到很多你济的学长\u0026hellip;) 和海归，甚至还有外国人，我直呼国际化。\n其中令我比较深刻的介绍单是一个大约30岁左右的男同胞的，和一般的介绍信不太一样，里面有一栏写的是\n无正式感情经历 阿哲，当时我的嘴角流露出一丝不易察觉，但又有点难绷的微笑，但我收住了，没有笑出来。这当然不是因为我受过专业的训练，而是我突然想到，我到了他这个年纪，估计也是这种情况（恼 \u0026hellip;\n当我正准备走出公园的时候，一个阿姨叫住了我\n小伙子，你几几年的？ 我当时愣了一下，但还是实话实讲了（这有什么不能说的？\n还好阿姨没有问出什么让我难堪的问题（还是好人多啊 ， 而是转头对旁边另一个阿姨说\n你看，我就说他还挺小的 之后我就遁出了人民公园。\n就目前而言，对于把谈恋爱结婚这种事直接简化变成硬件条件匹配的行为，我总是觉得哪里怪怪的，即使能”匹配成功“，对于其稳健性和可持久性也深表怀疑。但随着我挨社会皮鞭挨得越来越多，自己指不定过几年也要流入到这种”market“里面接受他人凌厉目光的审讯，想必总有一天，我会理解这些家长们的。\n上海历史博物馆 好！终于要步入本文的正题之一了。\n上海历史博物馆我路过过很多次了，在逛外滩/南京路，上海博物馆，大世界等多次的行程中都从曾其门口经过。但每次不是因疫情不开放，便是我忘记将其添加到自己的行程中，导致一次也没有进去参观过。\n上海历史博物馆最显著的特征便是其高耸的西式钟楼。\n这也让我在本此参观之前产生了不必要的错觉：\n一座钟楼嘛，看样子不是很大，估计一两个小时足够了 进去我才知道，这栋建筑的前身是旧上海的跑马总会，这高耸的钟楼，不过是其建筑的一个侧边缘而已\u0026hellip; ,对其馆藏丰富量的错误估计导致我今天并没有完整细致地逛完这座博物馆，下次一定！\n前厅 博物馆的一层大厅是一个花轿，如下图\n根据介绍，这是一位20世纪初浙江商人请十余位木雕匠人历时10年制作的，结婚所用的花轿，需8个人抬。其上饰品为朱金漆木雕、琉璃画等。根据讲解员的介绍，木雕是分部分组装而成的，再于其上镶上金银纹饰；而琉璃画的制作，则需要匠人在背面作画（比如面向观众的一面琉璃画需匠人在其背面绘制）。\n上海古代史 史前 上海地区的古代历史可以追溯到公元前4000-5000年左右的马家浜文化，历经崧泽，良渚，钱山漾等多个时期的演变才进入周代。其中以良渚文化历史最久，也最为知名。\n","permalink":"https://Moondok.github.io/blogs/posts/life/2023_10_22_map/","summary":"","title":"记2023下半年的第一次进城"},{"content":"Greetings! This is CHEN Kaixu (陳開煦) . Currently I am an undergraduate of Tongji Univ.\nThis blog is built to record some technique details during my study. Also , I will record some interesting moments here.\n","permalink":"https://Moondok.github.io/blogs/about/","summary":"Greetings! This is CHEN Kaixu (陳開煦) . Currently I am an undergraduate of Tongji Univ. This blog is built to record some technique details during my study. Also , I will record some interesting moments here.","title":"🙋🏻‍♂️ About"}]