[OS_7] 訪問操作系統對象 | offset | FHS | Handle

實驗代碼可以看去年暑假的這篇文章:【Linux】進程間通信:詳解 VSCode使用 | 匿名管道


我們已經知道,進程從 execve 后的初始狀態開始,可以通過 mmap 改變自己的地址空間,通過 fork 創建新的進程,再通過 execve 執行新的程序——我們慢慢已經開始理解 “操作系統上的應用生態“ 并沒有魔法了。

本講內容:操作系統還必須給應用程序提供訪問操作系統對象的機制。當然,我們可以直接以 API 的形式提供,例如 Win32 API 包含 “RegOpenKeyEx” 訪問注冊表。這節課我們學習 UNIX 的 “Everything is a file” 帶來的方便 (和不便)。


上一篇文章當中我們講到過,邊寫代碼邊測試非常的重要,那么我們該如何實現測試呢

明確需求

  • 我們需要一個狀態 (Everything is a state machine)
    • tests 里有全部測試用例程序還沒結束
  • 需要遵守 libc 的約定
    • tk_add_test 和 tk_register_hook 執行順序不確定
    • System test 里還可能調用 atexit()

解決問題

  • tk_register_hook 時立即創建一個進程
    • 等程序 atexit() 的時候把 tests 送過來
    • 程序之間交換數據:“serialization”(序列化)

Prompt: 有哪些對象 serialization 的通用機制?

常見的對象序列化機制包括:

  1. Java 序列化:使用 Serializable 接口。
  2. JSON:輕量級數據交換格式,廣泛用于 Web 開發。
  3. XML:可擴展標記語言,適用于復雜數據結構。
  4. Protocol Buffers:Google 的高效二進制序列化格式。
  5. Avro:Apache 的數據序列化系統,支持動態模式。
  6. MessagePack:二進制 JSON 格式,更高效。
  7. Thrift:Facebook 開發的跨語言序列化框架。

根據需求選擇合適的機制。

在這個例子里,因為是完全相同的進程地址空間

  • 我們直接做了一個 “內存復制”,保留了 argv 中指向只讀數據的指針。
  • 對于跨應用的對象序列化,則需要做字符串的 deep copy。

我們的做法:利用編程語言機制 hack

  • 并不是好的 practice (但有時候需要)

方法一:請開發者主動調用 API

  • 在 main 里 run_all_tests()
  • 之前學習機 gtest 的時候用的就是這種方法

方法二:提供一個特別的編譯器

  • JavaScript: 這個我懂

方法三:更好的編程語言

  • JVMTI: Tool Interface

testkit: Writing test cases fearlessly! 這是用于實驗的第一個測試框架:支持單元測試和系統測試,自動注冊測試用例并在程序退出后運行。

最重要的特點是它使用簡單:只需要包含 testkit.h,并且鏈接 testkit.c 即可。

沒有測試過的代碼,都是有可能存在問題的!


操作系統中的對象

進程

  • 進程 = 狀態機
  • 進程管理 API: fork, execve, exit

連續的內存段

  • 我們可以把 “連續的內存段” 看作一個對象
    • 可以在進程間共享
    • 也可以映射文件
  • 內存管理 API: mmap, munmap, mprotect, msync

操作系統肯定還有其他對象的!

是如何訪問操作系統對象的呢,那通過文件來訪問操作系統的對象

  • 文件像鍵盤顯示器也都可以理解為文件,哦我好像知道了
  • 相當于是平時寫的代碼生成了程序和進程,跑在操作系統這個環境上
  • 然后通過文件來訪問這些東西,讀取到的結果就是,例如是以我們顯示器也是一個文件,來顯示出來

7.1 文件描述符

文件和設備

文件:有 “名字” 的數據對象

  • 字節流 (終端,random)
  • 字節序列 (普通文件)

文件描述符

  • 指向操作系統對象的 “指針”
    • Everything is a file
    • 通過指針可以訪問 “一切”
  • 對象的訪問都需要指針
    • open, close, read/write (解引用), lseek (指針內賦值/運算), dup (指針間賦值)

文件描述符:訪問文件的 “指針”

  • open
    • p = malloc(sizeof(FileDescriptor));
  • close
    • delete(p);
  • read/write
    • *(p.data++);
  • lseek
    • p.data += offset;
  • dup
    • q = p;

在去年暑假,我們手寫 shell 的時候,有詳細寫過這部分的代碼,感興趣的可以去看一下

訪問操作系統中的對象--文件描述符

  • 總是分配最小的未使用描述符
  • 0, 1, 2 是標準輸入、輸出和錯誤
  • 新打開的文件從 3 開始分配
    • 文件描述符是進程文件描述符表的索引
    • 關閉文件后,該描述符號可以被重新分配
    • Linux 下一切皆文件

進程能打開多少文件?

  • ulimit -n (進程限制)
  • sysctl fs.file-max (系統限制)

文件描述符中的 offset

文件描述符是 “進程狀態的” 的一部分

  • 保存在操作系統中;程序只能通過整數編號訪問
  • 文件描述符自帶一個 offset

Quiz: fork() 和 dup()(共享) 之后,文件描述符共享 offset 嗎?

  • 這就是 fork() 看似優雅,實際復雜的地方

場景

是否共享 offset

原因

獨立打開同一文件

? 不共享

每個 open()

生成獨立文件表項

fork()

? 共享(繼承描述符)

子進程復制父進程文件表項,所以他們打開同一文件的話,不會實現覆蓋,頂多出現交叉寫入

dup()

? 共享

描述符指向同一文件表項

文件描述符:文件描述符是指向操作系統對象的 “指針”——系統調用通過這個指針 (fd) 確定進程希望訪問操作系統中的哪個對象。我們有 open, close, read/write, lseek, dup 管理文件描述符。


Windows 中的文件描述符

Handle (把手;握把;把柄)

  • 比 file descriptor 更像 “指針”
  • 你有一個 “handle” 在我手上,我就可以更好地控制你

Windows 的進程創建

面向工程的設計

  • 默認 handle 是不繼承的 (和 UNIX 默認繼承相反)
    • 可以在創建時設置 bInheritHandles,或者運行時修改
    • “最小權限原則”
  • lpStartupInfo 用于配置 stdin, stdout, stderr

(參考) Windows進程創建的工程化設計核心要點

1. 默認不繼承句柄
  • 安全設計新進程默認不繼承父進程的資源訪問權限(句柄),防止意外泄露。
  • 按需授權:通過參數 bInheritHandles 或運行時調整,顯式指定需共享的資源。
2. 集中式配置入口
  • 統一管理STARTUPINFO 結構體統一配置子進程的標準輸入/輸出/錯誤流,避免參數分散。
  • 模塊化擴展:通過結構化字段支持未來功能擴展,降低接口變動風險。
3. 安全與功能的工程權衡
  • 安全優先:相比UNIX默認繼承的便利性,Windows更強調最小權限原則(僅開放必要權限)。
  • 靈活控制:開發者可精準指定共享資源,平衡功能需求與安全風險。

Linux 引入了 O_CLOEXEC

  • fcntl(fd, F_SETFD, FD_CLOEXEC)
//對fd進行各種操作,成功返回0,失敗返回-1設errno
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... );		//...表示可變長參數
/*cmd:
Adversory record locking:
F_SETLK(struct flock*)	//設建議鎖
F_SETLKW(struct flock*)	//設建議鎖,如果文件上有沖突的鎖,且在等待的時候捕獲了一個信號,則調用被打斷并在信號捕獲之后立即返回一個錯誤,如果等待期間沒有信號,則一直等待 
F_GETLK(struct flock*)	//嘗試放鎖,如果能放鎖,則不會放鎖,而是返回一個含有F_UNLCK而其他不變的l_type類型,如果不能放鎖,那么fcntl()會將新類型的鎖加在文件上,并把當前PID留在鎖上
Duplicating a file descriptor:
F_DUPFD (int)		//找到>=arg的最小的可以使用的文件描述符,并把這個文件描述符用作fd的一個副本
F_DUPFD_CLOEXEC(int)//和F_DUPFD一樣,除了會在新的文件描述符上設置close-on-exec
F_GETFD (void)		//讀取fd的flag,忽略arg的值
F_SETFD (int)		//將fd的flags設置成arg的值.
F_GETFL (void)		//讀取fd的Access Mode和其他的file status flags; 忽略arg
F_SETFL (long)		//設置file status flags為arg
F_GETOWN(void)		//返回fd上接受SIGIO和SIGURG的PID或進程組ID
F_SETOWN(int)		//設置fd上接受SIGIO和SIGURG的PID或進程組ID為arg
F_GETOWN_EX(struct f_owner_ex*)	//返回當前文件被之前的F_SETOWN_EX操作定義的文件描述符R
F_SETOWN_EX(struct f_owner_ex*)	//和F_SETOWN類似,允許調用程序將fd的I/O信號處理權限直接交給一個線程,進程或進程組
F_GETSIG(void)		//當文件的輸入輸出可用時返回一個信號
F_SETSIG(int)		//當文件的輸入輸出可用時發送arg指定的信號
*//*…: 	
可選參素,是否需要得看cmd,如果是加鎖,這里應是struct flock*
struct flock {short l_type;	//%d Type of lock: F_RDLCK(讀鎖), F_WRLCK(寫鎖), F_UNLCK(解鎖)short l_whence;	//%d How to interpret l_start, 加鎖的位置參考標準:SEEK_SET, SEEK_CUR, SEEK_ENDoff_t l_start;  //%ld Starting offset for lock, 	加鎖的起始位置off_t l_len;    //%ld Number of bytes to lock , 鎖定的字節數pid_t l_pid;   	// PID of process blocking our lock, (F_GETLK only)加鎖的進程號,,默認給-1
};
*/

  • 文件描述符:文件描述符是指向操作系統對象的 “指針”——系統調用通過這個指針 (fd) 確定進程希望訪問操作系統中的哪個對象。

Filesystem Hierarchy Standard --FHS

  • enables software and user to predict the location of installed files and directories: 例如 macOS 就不遵循 FHS

只要拷對了文件,操作系統就能正常執行啦!

  1. 創建 UEFI 分區,并復制正確的 Loader
  2. 創建文件系統
    • mkfs (格式化)
  1. cp -ar 把文件正確復制 (保留權限)
    • 注意 fstab 里的 UUID
    • 就得到了一個可以正常啟動的系統盤!
  1. 運行時掛載必要的其他文件系統
    • 磁盤上的 /dev, /proc, ... 都是空的
    • mount -t proc proc /mount/point 可以 “創建” procfs

對于Linux制作系統盤的實驗前文有寫過,感興趣的可以找著看一下Linux 系統盤制作 | 引導加載器(GRUB 為例)| mount

操作系統給了我們很多API,可以創建各種各樣的對象


任何 “可讀寫” 的東西都可以是文件

真實的設備

  • /dev/sda
  • /dev/tty

虛擬的設備 (文件)

  • /dev/urandom (隨機數), /dev/null (黑洞), ...
    • 它們并沒有實際的 “文件”
    • 操作系統為虛擬的設備 (文件)實現了特別的 read 和 write 操作

      • /drivers/char/mem.c
      • 發現的一些有意思的事情:甚至可以通過 /sys/class/backlight 控制屏幕亮度
  • procfs 也是用類似的方式實現的

管道:一個特殊的 “文件” (流)

  • 由讀者/寫者共享
    • 讀口:支持 read
    • 寫口:支持 write

匿名管道

(Unix得圖靈獎的一個重要原因🙂

  • 返回兩個文件描述符
  • 進程同時擁有讀口和寫口
    • 看起來沒用?不,fork 一下就有用了 (testkit)

  • 然后甚至還可以再結合dup,來實現對 0 1 2 指向替代也可以
  • ls | wc -l (這就是一個管道的應用)

?理解管道的意義:

  • UNIX 管道:UNIX 管道 (pipe) 是一種典型的進程間通信機制,允許數據在不同的進程之間單向流動。
  • 管道可以被視為一種特殊的文件,其中一個進程將數據寫入管道的一端,而另一個進程從另一端讀取數據。
  • 管道實現了父子進程當中,先后的同步

?sum

進程管理

  • fork, execve, waitpid, exit

內存管理

  • mmap, munmap, mprotect, msync

文件管理

  • open, close, read, write, lseek, dup

  • mkfifo 命名管道,可以屬于某一個文件
  • pipe 匿名管道。

  • proc
  • sys
  • Everything is a File ...?

例如來和 ag 結合,cat readme.md | ag 就可以問AI 啦


一切皆文件的好處

一套 API 訪問所有對象

  • 一切都可以 | grep
    • Introducing ag -g

同時,UNIX Shell 的語法廣受詬病

  • 稍大一些的項目就應該用更好的語言 (Python, Rust!)
  • 但是:We all love quick & dirty!
ls -l /proc/*/fd/* 2>/dev/null | awk '{print $(NF-2), $(NF-1), $NF}'
grep -s VmRSS /proc/*[0-9]/status | awk '{sum += $2} END {print sum " kB"}'

Prompt: 什么是 glob patterns? 有什么實際中常用的擴展?

Glob patterns 是一種用于匹配文件路徑名的模式,常用于文件搜索和操作。常見的通配符包括:

  • *:匹配任意數量的字符(包括零個)
  • ?:匹配單個字符
  • []:匹配指定范圍內的字符

實際中常用的擴展包括:

  • **:遞歸匹配任意層級的子目錄
  • {}:匹配多個模式,如 {a,b,c} 匹配 abc
  • !(pattern):排除指定模式

這些擴展增強了 glob patterns 的靈活性和功能性。


字節流

  • 順序讀/順序寫
    • 沒有數據時等待
    • 典型代表:管道
    • 但是會存在操作系統對offset默默地移動...

字節序列

  • 其實就有一點點不方便了
    • 需要到處 lseek 再 read/write
      • mmap 不香嗎?指針指哪打哪
      • madvise, msync 提供了更精細的控制

lseek 用于重新定位文件偏移量(文件指針位置),支持三種定位模式:

  • SEEK_SET:絕對定位(從文件頭開始偏移)
  • SEEK_CUR:相對定位(從當前位置偏移)
  • SEEK_END:從文件末尾偏移

補充說明:該函數不會觸發任何物理 I/O 操作,僅修改內核中的文件偏移量記錄。

優點

  • 優雅,文本接口,就是好用

缺點

  • 和各種 API 緊密耦合
    • A fork() in the road

  • im ple men ta tion 實現
  • al ter na tives 替代方案
  • con flates 合并

如果我fork出的父子進程,同時寫Hello和world,它會是覆蓋呢還是實現延續呢?

  1. 進程獨立性
    fork()會創建子進程,父子進程的內存空間是獨立的,但文件描述符(如標準輸出)是共享的。因此,兩者的輸出會混合到同一個目標中,但不會直接覆蓋(每個write操作是原子的)
  2. 輸出順序不確定
    父子進程的執行順序由操作系統調度決定。可能的結果包括:
    • 父進程先輸出“Hello”,子進程后輸出“World” → HelloWorld
    • 子進程先輸出“World”,父進程后輸出“Hello” → WorldHello
    • 兩者交替執行,導致字符交錯(如HWeolrllod

  • 對高速設備不夠友好(why)
    • 額外的延遲和內存拷貝
    • 單線程 I/O

Any problem in computer science can be solved with another level of indirection. (Butler Lampson)

  • Windows NT: Win32 API → POSIX 子系統
    • Windows Subsystem for Linux (WSL)
  • macOS: Cocoa API → BSD 子系統
  • Fuchsia: Zircon 微內核 → POSIX 兼容層

兼容當然沒法做到 100%

  • sysfs, procfs 就是沒法兼容
  • 優雅的 WSL1 已經暴斃
    • “Windows Subsystem for Linux”
    • “Linux Subsystem for Windows” (wine)
    • 對硬件做抽象,給應用程序提供服務

拓展: OpenHarmony


對于 硬件和軟件
  • “初學FPGA,突然頓悟何為“硬件的并行化思維”的美妙,那一瞬之后,就像打通了任督二脈,之后不管是看代碼還是寫代碼都變得十分順暢。更關鍵的是我的認知也得到了提升:那是我第一次認知到不同思維模式會對coding產生如此之大的區別。
  • 其實反過來各種語言也在(強迫)塑造人的思維模式,比如cuda之類的并行編程要求程序員轉換思維模式。HDL也一樣。人發明工具,然后被工具改變。
  • 以除法為例,軟件工程師代碼的除法: int val=3300/256 ; 硬件工程師:int val = 3300》8;“

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/77533.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/77533.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/77533.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關于TCP三次握手和四次揮手過程中的狀態機、使用三次握手和四次揮手的原因、擁塞控制

關于傳輸層中的TCP協議&#xff0c;我們在之前的博客中對其報文格式、三次握手、四次揮手、流量控制、數據傳輸等機制進行了具體說明&#xff0c;接下來在前面所學的基礎上&#xff0c;我們再來講講TCP中三次握手和四次揮手各階段所處的狀態機以及為什么要使用三次握手和四次揮…

學習筆記二十——Rust trait

&#x1f9e9; Rust Trait 徹底搞懂版 &#x1f440; 目標讀者&#xff1a;對 Rust 完全陌生&#xff0c;但想真正明白 “Trait、Trait Bound、孤島法則” 在做什么、怎么用、為什么這樣設計。 &#x1f6e0; 方法&#xff1a; 先給“心里模型”——用生活類比把抽象概念掰開揉…

es 混合檢索多向量

在結合向量相似度檢索的同時,可以通過 bool 查詢的 filter 或 must 子句實現關鍵詞過濾。以下是一個同時包含 關鍵詞匹配 和 多向量相似度計算 的完整示例: 參考博文:ES集群多向量字段檢索及混合檢索方法-CSDN博客 示例:帶關鍵詞過濾的多向量聯合檢索 GET /my_index/_sea…

HTML5好看的水果蔬菜在線商城網站源碼系列模板4

文章目錄 1.設計來源1.1 主界面1.2 關于我們1.3 商品信息1.4 新聞資訊1.5 聯系我們1.5 登錄注冊 2.效果和源碼2.1 動態效果2.2 源代碼 源碼下載 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/147264262 HTML5好看的水果…

Kubernetes(k8s)學習筆記(二)--k8s 集群安裝

1、kubeadm kubeadm 是官方社區推出的一個用于快速部署 kubernetes 集群的工具。這個工具能通過兩條指令完成一個 kubernetes 集群的部署&#xff1a; 1.1 創建一個 Master 節點$ kubeadm init 1.2 將一個 Node 節點加入到當前集群中$ kubeadm join <Master 節點的 IP 和…

AI數據分析的優勢分析

隨著科技的飛速發展&#xff0c;人工智能&#xff08;AI&#xff09;已經深入滲透到數據分析領域&#xff0c;為各行各業帶來了前所未有的變革。AI數據分析作為一種新興的技術手段&#xff0c;通過運用機器學習、深度學習等算法對海量數據進行挖掘和分析&#xff0c;顯著提升了…

leetcode(01)森林中的兔子

今天開始記錄刷題的過程&#xff0c;每天記錄自己刷題的題目和自己的解法&#xff0c;歡迎朋友們給出更多更好的解法。 森林中的兔子 森林中有未知數量的兔子&#xff0c;提問其中若干只兔子“還有多少只兔子與你&#xff08;被提問的兔子&#xff09;顏色相同”。將答案收集到…

基于SpringBoot+Vue實現的旅游景點預約平臺功能一

一、前言介紹&#xff1a; 1.1 項目摘要 隨著人們生活水平的提高和休閑時間的增多&#xff0c;旅游已經成為人們生活中不可或缺的一部分。旅游業作為全球經濟的重要支柱&#xff0c;其發展趨勢呈現出數字化、網絡化和智能化的特點。傳統的旅游服務方式&#xff0c;如人工預約…

【支付】支付寶支付

下面為你詳細介紹使用 Spring Boot 對接支付寶支付&#xff0c;實現支付與退款功能的具體步驟和代碼示例。 添加依賴 在 pom.xml 里添加支付寶 SDK 依賴&#xff1a; <dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframewo…

shell 正則表達式與文本處理器

目錄 前言 一、正則表達式 &#xff08;一&#xff09;定義與用途 &#xff08;二&#xff09;基礎正則表達式 &#xff08;三&#xff09;基礎正則表達式元字符 &#xff08;四&#xff09;擴展正則表達式 二、文本處理器&#xff1a;Shell 編程的得力助手 &#xff0…

ASP.NET Core 最小 API:極簡開發,高效構建(上)

一、概述 構建最小 API&#xff0c;以創建具有最小依賴項的 HTTP API。 它們非常適合于需要在 ASP.NET Core 中僅包括最少文件、功能和依賴項的微服務和應用。 本文介紹使用 ASP.NET Core 生成最小 API 的基礎知識&#xff0c;將創建以下 API&#xff1a; API&#xff08;應用…

Apache Parquet 文件組織結構

簡要概述 Apache Parquet 是一個開源、列式存儲文件格式&#xff0c;最初由 Twitter 與 Cloudera 聯合開發&#xff0c;旨在提供高效的壓縮與編碼方案以支持大規模復雜數據的快速分析與處理。Parquet 文件采用分離式元數據設計 —— 在數據寫入完成后&#xff0c;再追加文件級…

IntelliJ IDEA 2025.1 發布 ,默認 K2 模式 | Android Studio 也將跟進

2025.1 版本已經發布&#xff0c;在此之前我們就聊過該版本的 《Terminal 又發布全新重構版本》&#xff0c;而現在 2025.1 中的 K2 模式也成為了默認選項。 可以預見&#xff0c;這個版本可能會包含不少大坑&#xff0c;為下個 Android Studio 祈禱。 首先有一點可以確定&…

云效部署實現Java項目自動化部署圖解

前言 記錄下使用云效部署Java項目&#xff0c;實現java項目一鍵化自動化部署。 云效流程說明&#xff1a; 1.云效拉取最新git代碼后 2.進行maven編譯打包后&#xff0c;上傳到指定服務器目錄 3.通過shell腳本&#xff0c;先kill java項目后&#xff0c;通過java -jar 啟動項…

國際數據加密算法(IDEA)詳解

以下是修正后的準確版本,已解決原文中的術語、符號及技術細節問題: ?國際數據加密算法(IDEA)? IDEA是一種分組加密算法,由Xuejia Lai(來學嘉)和James Massey于1990年設計。IDEA使用128位密鑰對64位明文分組進行加密,經過8輪迭代運算后生成64位密文分組。其安全性基于…

TensorFlow介紹

TensorFlow 是由 Google 開發 的開源機器學習框架&#xff0c;主要用于構建、訓練和部署機器學習模型。它支持深度學習、傳統機器學習和數值計算&#xff0c;適用于圖像識別、自然語言處理&#xff08;NLP&#xff09;、推薦系統、強化學習等多種任務。 核心特性 基于 數據流…

百級Function架構集成DeepSeek實踐:Go語言超大規模AI工具系統設計

一、百級Function系統的核心挑戰 1.1 代碼結構問題 代碼膨脹現象&#xff1a;單個文件超過2000行代碼路由邏輯復雜&#xff1a;巨型switch-case結構維護困難依賴管理失控&#xff1a;跨Function依賴難以追蹤 // 傳統實現方式的問題示例 switch functionName { case "fu…

嵌入式芯片中的 SRAM 內容細講

什么是 RAM&#xff1f; RAM 指的是“隨機存取”&#xff0c;意思是存儲單元都可以在相同的時間內被讀寫&#xff0c;和“順序訪問”&#xff08;如磁帶&#xff09;相對。 RAM 不等于 DRAM&#xff0c;而是一類統稱&#xff0c;包括 SRAM 和 DRAM 兩種主要類型。 靜態隨機存…

標準的JNI (Java Native Interface) 加載函數 JNI_OnLoad

1.JNI_OnLoad 在 Android Native 開發中&#xff0c;JNI_OnLoad 是動態注冊本地方法的標準入口點。以下是一個標準實現示例及其說明&#xff1a; JNI_OnLoad 標準實現 #include <jni.h> #include <string>// 聲明本地方法對應的 C/C 函數 jint native_add(JNIEnv…

算法導論思考題

2-1 在歸并排序中對小數組采用插入排序 c. 假定修改后的算法的最壞情況運行時間為 Θ \Theta Θ(nknlg(n/k))&#xff0c;要使修改后的算法與標準的歸并排序具有相同的運行時間&#xff0c;作為n的一個函數&#xff0c;借助 Θ \Theta Θ記號&#xff0c;k的最大值是什么&#…