一、调度
runqueue
内有两个哈希表(queue[140]
)
struct queue{int nr_active;int bitmap[5];task_struct* queue[140];
};
⬇️
struct queue array[2]
⬇️
struct runqueue{struct queue* active;//活跃的struct queue* expired;//过期的struct queue array[2]...
};
调度器在执行进程调度时,需要确保公平性和效率。
1. 调度过程
(1)队列管理:
- 系统维护两个队列:
active
和expired
。 - 初始状态下,
active
队列指向array[0]
,expired
队列指向array[1]
。
(2)调度情况:
- 进程运行结束:进程完成后,从
active
队列中移除。 - 时间片用完但进程未结束:将进程移至
expired
队列。 - 新进程产生:新进程加入
active
队列。
(3)队列交换:
- 当
active
队列中的进程数量逐渐减少并为空时,进行swap(&active, &expired)
操作,即交换两个队列的指针。 - 这样,原
expired
队列成为新的active
队列,重新开始调度。
2. Linux内核O(1)调度算法
- 优先级调度:在
active
队列中,按照进程的优先级进行调度,优先级高的进程先执行。 - 公平性:通过队列交换机制,确保低优先级的进程也有机会被调度,从而实现调度机会的均等化。
bitmap[5]
共占用32×5=160个比特位(可覆盖140个位置)作为位图使用。其中每位的值为0表示无进程,为1则表示相应位置上有进程。
for(int i = 0; i < 5; ++i){if (bitmap[i] == 0) continue;//一次就可以检测32个位置else {//32个比特位中确定是哪一个位置队列是不为空的//位操作...}
}
当然可以。以下是经过优化并补充细节的专业描述:
3. Linux中的双链表结构
(1)基本结构
Linux内核中使用的双链表结构主要用于高效地管理进程和其他内核对象。该结构仅包含指向前后节点的指针,而不直接携带数据:
node
实际为list_head
struct node {struct node* next; // 指向下一个节点struct node* prev; // 指向前一个节点
};
在task_struct
结构体中,这种双链表节点用于将进程与其他内核数据结构关联起来:
struct task_struct {// 进程的各种属性,如PID、状态等...// 链接字段,用于插入到不同的链表中struct node link;
};
为了使一个进程能够同时存在于多个数据结构中,例如全局进程列表、就绪队列或等待队列,task_struct
可以包含多个这样的双链表节点:
struct task_struct {// 进程的相关属性...// 不同场景下的链接字段struct node listnode; // 用于全局进程链表struct node queuenode; // 用于就绪队列struct node waitnode; // 用于等待队列...
};
通过这种方式,单个进程可以同时存在于全局链表以及任何其他需要的数据结构中,只需在task_struct
中添加对应的节点字段即可。这种方法提高了灵活性,允许内核有效地管理进程的不同状态和关系。
(2)访问其他属性的原理
表达式 (struct A*) (&c - &( (struct A*) 0 -> c ))
的目的是计算结构体 A
中成员 c
的偏移量,并将其转换为一个指向结构体 A
的指针。
(struct A*) 0
:这是将数字0强制类型转换为指向结构体A
的指针。这个指针并不指向实际的内存地址,它只是一个用于计算的基准点。(struct A*) 0 -> c
:这里通过基准点指针访问结构体A
的成员c
。由于基准点是一个假想的地址0,实际上并没有访问内存,而是表示了从结构体A
的起始地址到成员c
的偏移量。
这里的关键在于理解指针运算和结构体成员的偏移量:
(struct A*) 0
:数字0被强制转换为指向 struct A 的指针。这只是一个计算上的技巧,并不是真的要访问地址0处的结构体。
(struct A*) 0 -> c
:当我们使用这个指针来访问成员 c 时,编译器会计算成员 c 相对于结构体 A 起始地址的偏移量。这是编译器内部的行为,它知道每个成员在结构体中的位置,即使我们并没有创建一个实际的结构体实例。
使用 offsetof 宏来获取结构体成员的偏移量,下面是 offsetof 宏的一个典型定义:#define offsetof(type, member) ((size_t)(&((type*)0)->member))
&( (struct A*) 0 -> c )
:这是取成员c
的地址。由于指针是0,这个地址实际上就是成员c
相对于结构体A
起始地址的偏移量。&c
:这是取实际结构体实例中成员c
的地址。&c - &( (struct A*) 0 -> c )
:这是将实际成员c
的地址减去成员c
的偏移量。由于我们已经通过(struct A*) 0 -> c
计算了从结构体A
起始地址到c
的偏移量,这里的差值实际上就是结构体A
起始地址的值。
#define who(type, x) ( type* (&x - ( (type*) 0 -> x ) ) )
访问其他属性:
who(struct task_struct, link) -> pri
(或者pid
等等)
二、命令行参数
1. 引入
把main
函数的参数
int argc
(参数的个数), char* argv[]
(参数的清单)
称为命令行参数列表
#include <stdio.h>int main(int argc, char* argv[]){printf("argc: %d\n", argc);for(int i = 0; i < argc; ++i){printf("argv[%d]: %s\n", i, argv[i]);}return 0;
}
$ ./para abcdef xyz
argc: 3
argv[0]: ./para
argv[1]: abcdef
argv[2]: xyz
存在的原因:
同一个程序,可以根据命令行参数、根据选项的不同,表现出不同的功能,比如指令中选项的实现
简例:
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{if(argc != 2){printf("Usage: ./code -opt\n");}else if (strcmp(argv[1], "-opt1") == 0){printf("功能1\n");}else if (strcmp(argv[1], "-opt2") == 0){printf("功能2\n");}else if (strcmp(argv[1], "-opt3") == 0){printf("功能3\n");}else{printf("默认功能\n");}return 0;
}
2. main参数的传递
输入命令 + 选项
(字符串),首先被shell拿到按照空格打散,形成一张表(argv)和元素个数(argc)
命令行启动的程序,父进程都是shell
对于数据(尤其是只读的),子进程也能看到,把参数传给main函数即可
编译器、操作系统和加载器不是互相割裂的,彼此之间是有关系的
三、环境变量
1. 引入
#include <stdio.h>
#include <string.h>int main(int argc, char* argv[], char* env[])
{for(int i = 0; env[i]; ++i){printf("env[%d]: %s\n", i, env[i]);}return 0;
}
查看环境变量:
env[0]: SHELL=/bin/bash
env[1]: HISTSIZE=1000
env[2]: HISTTIMEFORMAT=%F %T root
env[3]: PWD=/home/usr1/mylinux/F0923
env[4]: LOGNAME=usr1
env[5]: XDG_SESSION_TYPE=tty
env[6]: MOTD_SHOWN=pam
env[7]: HOME=/home/usr1
env[8]: LANG=en_US.UTF-8
......
以key=value
方式构建的,具有“全局”属性的变量,叫做全局变量
2. 常见的环境变量
【Q】为什么系统知道命令在/usr/bin
路径下?
【A】PATH
(指定命令的搜索路径)环境变量告知了shell该何处查找
usr1$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
:
是路径分隔符,PATH
是系统可执行文件的搜索路径集合
如果不想带路径输入命令让我的程序运行起来,可以:
(1)把可执行文件拷贝到/usr/bin
目录下
(2)把自己的路径添加到PATH
中
追加路径:PATH=$PATH:当前路径
(每次重启会重置回初始状态)
【注】如果直接输入PATH=当前路径
添加后再输入其他命令会发现其他命令找不到了,因为这种写法是覆盖式的
【Q】环境变量的初始内容从何而来?
【A】开始都是在系统的配置文件中
登录 ➡️ 启动一个shell进程 ➡️ 读取用户和系统相关的环境变量的配置文件 ➡️ 形成自己的环境变量表 ➡️ 子进程