前言
os 属于runtime模块下的os子模块;
os 定义了操作系统的接口,这包括传统的操作系统服务(如时间、I/O),以及其他与系统相关的功能,其中可能包含系统特定的代码,里面定义的方法大部分都是静态方法,根据所在系统的不同,会有不同的实现。
os
openjdk-jdk8u/hotspot/src/share/vm/runtime/os.hpp
openjdk-jdk8u/hotspot/src/share/vm/runtime/os.cpp
linux下特殊实现 os_linux
openjdk-jdk8u/hotspot/src/os/linux/vm/os_linux.hpp
openjdk-jdk8u/hotspot/src/os/linux/vm/os_linux.cpp
os::init() os/linux下实现
包括如下一些步骤:
- 设置内存页大小
- 初始化系统信息(处理器、物理内存等)
- 初始化操作系统信息
- 获取main线程的句柄
- 初始化系统时钟
- ...
void os::init(void) {
char dummy; /* used to get a guess on initial stack address */
// 在 LinuxThreads 环境下,JavaMain 线程的 pid(原始线程)
// 与 Java 启动器线程的 pid 不同。
// 因此,在 Linux 系统上,启动器线程的 pid 通过 sun.java.launcher.pid 属性传递给虚拟机。
// 如果该属性正确传递,则使用此属性而不是 getpid()。
// 参见 Bug 6351349。
// LinuxThreads 是 Linux 上早期实现 POSIX 线程(pthread)的一个线程库,用于在多核处理器上运行并发程序。
// 它是 Linux 系统中最早的一种多线程编程模型,但由于其设计局限性,后来被 NPTL(Native POSIX Thread Library)取代
pid_t java_launcher_pid = (pid_t) Arguments::sun_java_launcher_pid();
_initial_pid = (java_launcher_pid > 0) ? java_launcher_pid : getpid();
// 系统调用sysconf获取属性_SC_CLK_TCK的值,这个值表示时钟每秒的滴答数
clock_tics_per_sec = sysconf(_SC_CLK_TCK);
// 设置随机数的种子 1234567
init_random(1234567);
// 钩子函数,目前没有具体实现
ThreadCritical::initialize();
// 系统调用sysconf获取内存页大小,并设置,供后续使用
Linux::set_page_size(sysconf(_SC_PAGESIZE));
if (Linux::page_size() == -1) {
fatal(err_msg("os_linux.cpp: os::init: sysconf failed (%s)", strerror(errno)));
}
// 设置到 _page_sizes 数组中
init_page_sizes((size_t) Linux::page_size());
// 获取并设置cpu核数; 获取并设置系统总物理内存数
Linux::initialize_system_info();
// _main_thread 指向当前线程,也就是main线程
Linux::_main_thread = pthread_self();
// 时钟处理初始化
Linux::clock_init();
initial_time_count = javaTimeNanos();
// 初始化全局的 pthread条件变量属性 _condattr
int status;
pthread_condattr_t* _condattr = os::Linux::condAttr();
if ((status = pthread_condattr_init(_condattr)) != 0) {
fatal(err_msg("pthread_condattr_init: %s", strerror(status)));
}
// CLOCK_REALTIME: 使用系统的实时时钟来计算等待时间。
// CLOCK_MONOTONIC: 使用单调时钟来计算等待时间,不受系统时间调整的影响
if (Linux::supports_monotonic_clock()) {
// 设置线程条件变量_condatt的时钟类型
if ((status = pthread_condattr_setclock(_condattr, CLOCK_MONOTONIC)) != 0) {
if (status == EINVAL) {
warning("Unable to use monotonic clock with relative timed-waits" \
" - changes to the time-of-day clock may have adverse affects");
} else {
fatal(err_msg("pthread_condattr_setclock: %s", strerror(status)));
}
}
}
// else it defaults to CLOCK_REALTIME
// 初始化互斥变量,作为后面互斥锁使用
pthread_mutex_init(&dl_mutex, NULL);
// 如果page size 大于8k,就要调整虚拟机栈保护页大小,
// 这里我们暂时只考虑page_size 4K的情况,所以这里忽略掉
if (vm_page_size() > (int)Linux::vm_default_page_size()) {// const int os::Linux::_vm_default_page_size = (8 * K);
StackYellowPages = 1;
StackRedPages = 1;
StackShadowPages = round_to((StackShadowPages*Linux::vm_default_page_size()), vm_page_size()) / vm_page_size();
}
// dlsym 在之前从 dlopen() 装入的模块导出的符号中,查找指定符号
// pthread_setname_np: 该函数用来设置线程名,默认创建的线程名都是程序名
Linux::_pthread_setname_np = (int(*)(pthread_t, const char*))dlsym(RTLD_DEFAULT, "pthread_setname_np");
}
RTLD_DEFAULT
是 dlsym
函数的一个特殊标志,它告诉 dlsym
在整个全局符号表中查找符号,而不仅仅是从指定的动态库中。它通常用于查找那些已经加载的全局符号。
当你想查找一个全局的符号,而不关心它来自哪个动态库时,使用 RTLD_DEFAULT
作为 dlsym
的句柄。
Linux::initialize_system_info()
hotspot/src/os/linux/vm/os_linux.cpp
// 在大多数 Linux 版本中,通过查看 /proc 文件系统来确定处理器数量存在一个 bug。
// 在 chroot 环境中,该系统调用返回 1。这会导致虚拟机表现得好像只有一个处理器,并省略锁定操作(参见is_MP() 调用)
void os::Linux::initialize_system_info() {
// 获取并设置cpu核数
set_processor_count(sysconf(_SC_NPROCESSORS_CONF));
if (processor_count() == 1) { // 这段代码同上面那段说明有关系
pid_t pid = os::Linux::gettid();
char fname[32];
jio_snprintf(fname, sizeof(fname), "/proc/%d", pid);
FILE *fp = fopen(fname, "r");
if (fp == NULL) {
unsafe_chroot_detected = true;
} else {
fclose(fp);
}
}
// 获取并设置系统总物理内存数
_physical_memory = (julong)sysconf(_SC_PHYS_PAGES) * (julong)sysconf(_SC_PAGESIZE);
assert(processor_count() > 0, "linux error");
}
附录
库函数 dlsym 说明
-
动态链接:
dlsym
用于获取已加载动态库中的符号地址,通常配合dlopen
和dlclose
一起使用,来动态加载和卸载共享库。 -
返回指针:
dlsym
返回指定符号的地址,调用者需要将其转换为相应类型的指针,以便调用该函数或访问变量。 -
错误处理:如果找不到指定的符号,
dlsym
返回NULL
,可以使用dlerror()
获取详细的错误信息。#include <dlfcn.h> #include <stdio.h> int main() { void *handle = dlopen("libm.so", RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); return 1; } double (*cosine)(double) = dlsym(handle, "cos"); if (!cosine) { fprintf(stderr, "%s\n", dlerror()); return 1; } printf("%f\n", cosine(2.0)); dlclose(handle); return 0; }
查看物理CPU核数
# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
查看每个物理CPU中core的个数
# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq
proc
proc 是 Processes(进程) 的缩写,/proc 是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。
这个目录的内容不在硬盘上而是在内存里,我们也可以直接修改里面的某些文件,比如可以通过下面的命令来屏蔽主机的ping命令,使别人无法ping你的机器:
可以通过修改 /proc/sys/net/ipv4/icmp_echo_ignore_all
文件来屏蔽主机的 ping 响应。具体的操作命令如下:
# 这条命令会使你的主机忽略所有 ICMP 回显请求(ping)。
# 屏蔽 ping 响应
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
# 如果需要恢复,让主机重新响应 ping 请求,可以执行
# 恢复 ping 响应
echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all