【为项目做准备】Linux操作系统day2

2024-09-05 04:12

本文主要是介绍【为项目做准备】Linux操作系统day2,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这两天学校的事情总压着,day2拖了好几天..day2内容是进程数据结构

  • 进程数据结构
    • 信号处理
    • 任务状态
    • 进程调度
    • 运行统计信息
    • 进程亲缘关系
    • 进程权限
      • 用户和组标识符(IDs)
      • linux capabilities
    • 内存管理
    • 文件与文件系统
    • 用户态与内核态
      • 用户态与内核态的转换
      • 函数调用栈
      • 内核栈和task_struct的关系

进程数据结构

进程or线程,在内核中,统一叫任务(Task),由一个统一的结构task_struct来管理。

怎么管理?

首先,需要一个链表,将所有任务串起来
struct list_head tasks;

接下来,具体看看任务包含哪些东西:

  1. 任务ID。

作为任务的唯一标识,但是task_struct里面涉及任务ID的,有好几个。

pid_t pid; // pid用于识别单个进程或者线程
pid_t tgid; // tgid用于表示线程组,所有属于同一线程组的线程共享相同的tgid
struct task_struct *group_leader;// 指向线程组的主线程,它的pid和tgid相同。用来代表整个线程组

为什么这么麻烦?因为进程和线程统一为任务了,会带来好几个麻烦。
第一个麻烦,是任务展示。
如果只有一个任务ID,那么用ps展示的时候,会展示所有线程,会非常长且复杂。但是用户可能只想看自己创建的线程
第二个麻烦,是下发指令。
加入用户希望进程结束运行,但是只有线程ID,那么就只能一个个线程去kill,多麻烦。所以他俩虽然都是任务,但是需要加以区分。

多线程的情况下:

  • pid 是进程标识符(Process ID),唯一地标识一个进程。对于多线程程序,所有线程共享相同的 pid,即所有线程都属于同一个进程。
  • tgid 是线程组标识符(Thread Group ID)。在 Linux 中,所有属于同一个线程组的线程共享相同的 tgid。这个值等于线程组的主线程(通常是创建线程组的那个线程)的 pid。所以,tgid 可以用来标识线程组的所有线程。
  • group_leader 指向线程组的主线程(线程组领导者)。在一个多线程程序中,group_leader 的 pid 和 tgid 是相同的。

信号处理

task_struct里面关于信号处理的字段。

  • signal 和 sighand:
    这两个字段指向signal_struct和sighand_struct结构体,分别用于管理与信号相关的状态信息和信号处理函数。
  • blocked,real_blocked,和 saved_sigmask:
    这些集合表示当前阻塞的信号、实际阻塞的信号和保存的信号掩码,用于控制哪些信号可以传递给进程。
  • pending:
    表示当前等待处理的信号。
  • sas_ss_sp、sas_ss_size 和 sas_ss_flags 用于管理在接收信号时可能使用的替代信号栈的信息。

任务状态

状态字段:task_struct 中的 state、exit_state 和 flags 字段描述任务的不同状态。

  • state:

任务的当前状态,例如运行(TASK_RUNNING)、可中断睡眠(TASK_INTERRUPTIBLE)、不可中断睡眠(TASK_UNINTERRUPTIBLE)等。

  • exit_state:

任务的退出状态,例如僵尸(EXIT_ZOMBIE)、死亡(EXIT_DEAD)等。

  • flags:

标志位,表示任务的不同属性,例如 PF_EXITING(正在退出)、PF_VCPU(虚拟CPU上运行)等。

进程调度

  • 调度相关字段:

包括是否在运行队列上(on_rq)
优先级(prio当前优先级、static_prio基本优先级、normal_prio正常优先级、rt_priority实时优先级)、调度器类(sched_class)等。

  • 调度实体:

sched_entity调度实体、sched_rt_entity实时调度实体 和 sched_dl_entity时间调度实体。
policy:调度策略,如SCHED_FIFO、SCHED_RR等。
cpus_allowed 和 nr_cpus_allowed:表示进程可以运行的CPU。

示例代码:
这段代码首先阻塞SIGINT信号,检查该信号是否处于待处理状态,然后恢复原来的信号掩码。这样的操作对于理解如何在实际应用中管理信号非常有帮助。

#include <signal.h>
#include <stdio.h>int main() {sigset_t newmask, oldmask, pendmask;// 设置新的信号掩码,阻塞SIGINTsigemptyset(&newmask);sigaddset(&newmask, SIGINT);if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {perror("Signal block error");}// 检查信号是否处于待处理状态if (sigpending(&pendmask) < 0) {perror("sigpending error");}if (sigismember(&pendmask, SIGINT)) {printf("SIGINT pending\n");}// 恢复旧的信号掩码if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {perror("Signal setmask error");}return 0;
}

运行统计信息

u64				utime;//用户态消耗的CPU时间
u64				stime;//内核态消耗的CPU时间
unsigned long			nvcsw;//自愿(voluntary)上下文切换计数
unsigned long			nivcsw;//非自愿(involuntary)上下文切换计数
u64				start_time;//进程启动时间,不包含睡眠时间
u64				real_start_time;//进程启动时间,包含睡眠时间

示例代码:
getrusage()函数,它是获取当前进程资源使用信息的标准方式。这些信息包括用户态和内核态消耗的CPU时间,以及自愿和非自愿的上下文切换次数。

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>int main() {struct rusage usage;// 获取当前进程的资源使用情况getrusage(RUSAGE_SELF, &usage);printf("User CPU time used: %ld.%06ld seconds\n", usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);printf("System CPU time used: %ld.%06ld seconds\n", usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);printf("Voluntary context switches: %ld\n", usage.ru_nvcsw);printf("Involuntary context switches: %ld\n", usage.ru_nivcsw);return 0;
}

进程亲缘关系

任何一个进程都有父进程。所以,整个进程其实就是一棵进程树。而拥有同一父进程的所有进程都具有兄弟关系。

struct task_struct __rcu *real_parent;
struct task_struct __rcu *parent;
struct list_head children;     
struct list_head sibling;     
  • parent指向其父进程。当它终止时,必须向它的父进程发送信号
  • children表示链表的头部。链表中的所有元素都是它的子进程。
  • sibling用于把当前进程插入到兄弟链表中。

通常情况下,real_parent和parent是一样的,但是也会有另外的情况存在。例如,bash创建一个进程,那进程的parent和real_parent就都是bash。如果在bash上使用GDB来debug一个进程,这个时候GDB是parent,bash是这个进程的real_parent。

示例代码:
getppid()函数来获取并打印当前进程的父进程ID。这对于理解进程之间的关系以及系统如何管理这些关系非常有用。

#include <stdio.h>
#include <unistd.h>int main() {pid_t ppid = getppid();  // 获取父进程IDprintf("Parent process ID: %d\n", ppid);return 0;
}

进程权限

用户和组标识符(IDs)

  1. 实际和有效用户/组ID(UID/GID)
  • uid/gid:启动进程的用户或组的实际ID。
  • euid/egid:决定进程权限的有效ID,用于大部分安全检查。
  • suid/sgid:在执行setuid或类似操作时保存的用户或组ID。
  • fsuid/fsgid:文件系统操作时使用的用户或组ID,主要用于特殊权限检查。

查看当前进程的uid和gid:

#include <unistd.h>
#include <stdio.h>
int main() {printf("Current UID: %d\n", getuid());printf("Current GID: %d\n", getgid());return 0;
}

使用setuid或者setgid提升或降低权限:

setuid程序允许用户以文件所有者的权限运行程序,这在需要临时提升权限以执行特定任务时非常有用。

示例代码:创建一个setuid的可执行文件

# 编译程序
gcc program.c -o program# 改变所有者到root
sudo chown root:root program# 设置setuid位
sudo chmod u+s program

系统调用和权限检查:

示例代码:在服务中临时更改权限

#include <sys/types.h>
#include <unistd.h>void temporary_drop_privileges(uid_t new_uid) {uid_t original_uid = getuid();// 临时降低权限if (setuid(new_uid) != -1) {// 执行低权限操作}// 恢复原始权限setuid(original_uid);
}

使用setuid()系统调用:

#include <sys/types.h>// 包含用于定义数据类型的系统类型头文件,如uid_t。
#include <unistd.h>// 提供对POSIX操作系统API的访问,如setuid。
#include <stdio.h>// 标准输入输出头文件,提供了输入输出函数如printf和perror。int main() {//定义了一个uid_t类型的变量new_uid并初始化为1001。uid_t是用来表示用户ID的数据类型uid_t new_uid = 1001;//setuid函数是一个系统调用,用来设置进程的实际用户ID。如果调用成功,函数返回0;如果失败,返回-1//这句代码是尝试将当前进程的用户ID设置为new_uid的值1001if (setuid(new_uid) == -1) {perror("Failed to change user ID");return 1;}printf("User ID changed successfully\n");return 0;
}

linux capabilities

细粒度权限控制:
Linux的能力(capabilities)将传统的root权限分解为更细粒度的权限集,允许对特定系统操作进行精细控制。

  1. 重要的能力标识(部分):
  • CAP_CHOWN: 改变文件所有者。
  • CAP_NET_BIND_SERVICE: 绑定到系统端口(1024以下)。
  • CAP_SYS_BOOT: 允许重启系统。
  1. 能力集合:
  • cap_effective: 实际有效的权限。
  • cap_permitted: 允许的权限。

示例代码:

#include <sys/capability.h>//包含了Linux能力(capabilities)相关的API定义,允许程序查询或修改进程的能力。
void check_capability(cap_value_t cap) {cap_t caps = cap_get_proc();//cap_get_proc()函数被调用以获取当前进程的能力对象。这个对象包含了进程的所有能力信息。cap_flag_value_t cap_val;cap_get_flag(caps, cap, CAP_EFFECTIVE, &cap_val);//cap_get_flag()函数用于检查指定能力(cap参数指定)在进程的有效能力集中是否被设置(CAP_EFFECTIVE表示正在查询有效能力集)。//根据cap_val的值,输出该能力是否被设置。如果被设置,cap_val将等于CAP_SET。if (cap_val == CAP_SET) {printf("Capability is set\n");} else {printf("Capability is not set\n");}cap_free(caps);
}int main() {//调用了check_capability(),并检查当前进程是否有绑定低编号端口的能力(CAP_NET_BIND_SERVICE)。//这是许多网络服务需要的权限,通常只有系统管理员(root)才具备。check_capability(CAP_NET_BIND_SERVICE);return 0;
}

内存管理

每个进程都有自己独立的虚拟内存空间,这需要有一个数据结构来表示,就是mm_struct。

struct mm_struct                *mm;
struct mm_struct                *active_mm;

文件与文件系统

/* Filesystem information: */
struct fs_struct                *fs;
/* Open file information: */
struct files_struct             *files;

用户态与内核态

用户态与内核态的转换

进程执行从用户态到内核态的转换通常发生在系统调用或中断发生时。这种状态转换涉及到以下两个关键成员变量:

  • thread_info - 这是一个结构体,存在于每个进程的内核栈的底部,包含了指向task_struct的指针以及其他线程特定的信息。
  • stack - 指向进程的内核栈,内核栈用于存储进入内核态时的临时数据,包括函数调用和中断处理的上下文。

函数调用栈

  • 用户态函数栈:在用户空间中,函数的调用和返回是通过栈实现的。栈用于存储局部变量、返回地址和函数参数。
  • 内核态函数栈:当进程进入内核态时,会使用独立的内核栈,这保证了内核操作的安全性和独立性。

内核栈和task_struct的关系

每个进程的内核栈顶部附近存储了一个thread_info结构体,该结构体中包含了一个指向对应task_struct的指针。
在某些架构中,可以直接通过内核栈地址找到task_struct,因为thread_info位于内核栈的底部。

示例代码1:
获取当前进程的task_struct
在Linux内核开发中,经常需要访问当前进程的task_struct。

#include <linux/sched.h>  // 包含 task_struct 和 current 宏
#include <linux/module.h>  // 包含内核模块的基本功能
//内核模块初始化函数
//它使用 current 宏获取当前进程的 task_struct,然后打印出该进程的名称(comm 字段)和进程ID(pid 字段)。这是诊断和监视当前运行环境非常有用的信息。
int init_module(void) {struct task_struct *curr = current;  // current 宏用于获取当前正在CPU上运行的进程的 task_struct 指针。printk(KERN_INFO "Current process is \"%s\" (pid %i)\n", curr->comm, curr->pid);return 0;
}
//当模块被卸载时执行的清理函数。此示例中,它仅打印一条消息表示模块正在被清理。
void cleanup_module(void) {printk(KERN_INFO "Module cleanup\n");
}
//指定模块的许可证类型,这对于模块的分发和使用有法律上的意义。"GPL"表示该模块遵守GNU通用公共许可证。
MODULE_LICENSE("GPL");

这段代码定义了一个内核模块,当模块加载时,它会打印当前进程的名称和PID。
在用户程序中,通常不需要直接处理栈,但是理解栈的工作方式有助于调试和性能优化。例如,递归函数或深层嵌套的函数调用可能会耗尽栈空间,导致栈溢出。

示例代码2:
查看进程的父进程信息

#include <linux/sched.h>
#include <linux/module.h>int init_module(void) {struct task_struct *parent;parent = current->real_parent;  // 获取当前进程的父进程的 task_structprintk(KERN_INFO "Parent process is \"%s\" (pid %i)\n", parent->comm, parent->pid);return 0;
}

这段代码将打印当前进程的父进程的名称和PID,有助于理解进程树的结构。

示例代码3:
监视进程状态变化

#include <linux/sched.h>
#include <linux/module.h>int init_module(void) {printk(KERN_INFO "Current process state: %ld\n", current->state);return 0;
}

这段代码查看并打印当前进程的状态(如运行、睡眠等)。state 字段是 task_struct 中用于追踪进程状态的关键成员。

这篇关于【为项目做准备】Linux操作系统day2的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1137911

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

一文教你如何将maven项目转成web项目

《一文教你如何将maven项目转成web项目》在软件开发过程中,有时我们需要将一个普通的Maven项目转换为Web项目,以便能够部署到Web容器中运行,本文将详细介绍如何通过简单的步骤完成这一转换过程... 目录准备工作步骤一:修改​​pom.XML​​1.1 添加​​packaging​​标签1.2 添加

tomcat多实例部署的项目实践

《tomcat多实例部署的项目实践》Tomcat多实例是指在一台设备上运行多个Tomcat服务,这些Tomcat相互独立,本文主要介绍了tomcat多实例部署的项目实践,具有一定的参考价值,感兴趣的可... 目录1.创建项目目录,测试文China编程件2js.创建实例的安装目录3.准备实例的配置文件4.编辑实例的

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

springboot集成Deepseek4j的项目实践

《springboot集成Deepseek4j的项目实践》本文主要介绍了springboot集成Deepseek4j的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录Deepseek4j快速开始Maven 依js赖基础配置基础使用示例1. 流式返回示例2. 进阶