【为项目做准备】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 中用于追踪进程状态的关键成员。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/878661.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

turbovnc 服务端、客户端安装

turbovnc 可以方便地远程登录带界面的linux系统&#xff0c;比如xbuntu、kali等&#xff1b;远程windows11系统&#xff0c;经过亲身测试体验&#xff0c;感觉还是不如windows自带的rdp服务&#xff08;mstsc命令连接&#xff09;好用。 一、安装客户端 下载最新版本的客户端…

力扣面试经典算法150题:接雨水

接雨水 今天的题目是力扣面试经典算法150题中的困难难度数组题目&#xff1a;分发糖果。 题目链接&#xff1a;https://leetcode.cn/problems/trapping-rain-water/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 给定 n 个非负整数表示每个宽度为…

常见的管理系统简称

1. ERP (Enterprise Resource Planning) - 企业资源计划 定义: ERP是一种集成了企业各个业务模块&#xff08;如财务、人力资源、生产、供应链、销售等&#xff09;的管理系统&#xff0c;旨在通过统一的平台来优化企业资源的配置和使用。 功能和特点: 集成性: ERP系统将企业…

python库pdf转word

要在 Python 中将 PDF 文件转换为 Word 文档&#xff08;.doc 或 .docx 格式&#xff09;&#xff0c;您可以使用几个不同的库来实现这一目标。这里介绍几种常用的库及其使用方法&#xff1a; 1. 使用 pdf2docx pdf2docx 是一个流行的 Python 库&#xff0c;用于将 PDF 文件转换…

【Rust】006-Rust 枚举与`match`、`if let`、`let else`

【Rust】006-Rust 枚举与match、if let、let else 文章目录 【Rust】006-Rust 枚举与match、if let、let else一、简介二、使用场景三、基本使用1、定义枚举2、使用枚举 四、功能详解1、带数据的枚举2、使用match进行模式匹配3、使用if let简化特定变体的处理4、使用let else处…

0904作业+思维导图

一、作业 &#xff08;将昨天的作业修改为标准模板类的&#xff09; 1、代码 #include <iostream> #include <stack> using namespace std; //队列模板类 template<typename T> class Queue { private:int max; //队列最大容量int num; //队列内…

pikachu文件包含漏洞靶场通关攻略

本地文件包含 首先&#xff0c;在靶场根目录下创建一个php文件&#xff0c;内容是phpinfo(); 其次&#xff0c;上传一个任意球星图片&#xff0c;会跳转到带有filename参数的php文件下 然后&#xff0c;将filename的参数改为可以访问到我们创建的php文件的地址 ../../../../…

【计算机】1GB与1Gb与MB的关系

背景 看到个规格书列的芯片规格参数有错误&#xff0c;给对方指出来了下&#xff0c;并做了下科普。做下记录。 关系 1 Gb&#xff08;Gigabit&#xff09;和1 GB&#xff08;Gigabyte&#xff09;之间的区别主要在于“b”和“B”的含义。通常&#xff1a; 1 Gb (Gigabit) 中…

TCP协议多进程多线程并发服务器

TCP多进程多线程并发服务器 1.多进程并发服务器 #include <myhead.h>#define SERPORT 6666 #define SERIP "192.168.0.136" #define BLACKLOG 10void hande(int a) {if(aSIGCHLD){while(waitpid(-1,NULL,WNOHANG)!-1);//回收僵尸进程} }int main(int argc, c…

【Grafana】Prometheus结合Grafana打造智能监控可视化平台

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Oracle 客户端 PL/SQL Developer 15.0.4 安装与使用

目录 官网下载与安装 切换中文与注册 连接Oracle数据库 tnsnames.ora 文件使用 Oracle 客户端 PL/SQL Developer 12.0.7 安装、数据导出、Oracle 执行/解释计划、for update。 官网下载与安装 1、官网&#xff1a;https://www.allroundautomations.com/products/pl-sql-d…

Redis的配置和启动+Redis Insight连接

一、安装 Redis的安装&#xff1a;从镜像站下载&#xff1a;索引 redis-local (huaweicloud.com)&#xff0c;然后将其传到Linux虚拟机中进行解压&#xff0c;解压之后需要下载gcc&#xff0c;因为Redis底层是用c写的&#xff0c;所以要编译一下生成redis文件&#xff0c;然后…

vite项目配置本地开发使用https访问

在Vite项目中启用HTTPS以安全地使用navigator.mediaDevices.getUserMedia() 引言 在现代Web开发中&#xff0c;保护用户隐私和数据安全是至关重要的。特别是在涉及到媒体捕获功能&#xff0c;如使用用户的摄像头或麦克风时&#xff0c;Web应用需要遵循严格的安全准则。naviga…

反向迭代器:reverse_iterator的实现

目录 前言 特点 注意事项 实现 构造函数 功能函数 在list与vector中的使用 vector list 前言 反向迭代器是一种在序列容器的末尾开始&#xff0c;并向前移动至序列开始处的迭代器。在C中&#xff0c;反向迭代器由标准库中的容器类提供&#xff0c;比如vector、list、d…

Ansible剧本编写指南:从简单任务到复杂自动化的实现

Ansible剧本编写指南&#xff1a;从简单任务到复杂自动化的实现 Ansible 是一个流行的开源自动化工具&#xff0c;被广泛用于配置管理、应用部署、任务自动化以及 IT 基础设施的编排。它的核心是简单且易于学习的 YAML 格式&#xff0c;使用户能够编写可重用、可维护的剧本&am…

Qt 字符串的编码方式,以及反斜杠加3个数字是什么编码\344\275\240,如何生成

Qt 字符串的编码方式 问题 总所周知&#xff0c;Qt的ui文件在编译时&#xff0c;会自动生成一个ui_xxxxx.h的头文件&#xff0c;打开一看&#xff0c;其实就是将摆放的控件new出来以及布局的代码。 只要用Qt提供的uic.exe工具&#xff0c;自己也可以将ui文件输出为代码文件…

rust feature 简介

Rust 的 feature 是一种机制&#xff0c;用于在编译时选择性地启用或禁用代码的某些部分。通过 feature&#xff0c;你可以在 Cargo.toml 中定义哪些功能需要启用&#xff0c;并在代码中通过条件编译来控制代码的编译与否。下面是 feature 机制的详解&#xff1a; 1. 基本概念…

设计模式 18 备忘录模式

设计模式 18 创建型模式&#xff08;5&#xff09;&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式结构型模式&#xff08;7&#xff09;&#xff1a;适配器模式、桥接模式、组合模式、装饰者模式、外观模式、享元模式、代理模式行为型模式&#xff…

c# 笔记 winform添加右键菜单,获取文件大小 ,多条件排序OrderBy、ThenBy,list<double>截取前5个

Winform右键菜单‌ 要在C# Winform应用程序中添加右键菜单&#xff0c;‌你可以按照以下步骤操作&#xff1a;‌ 1.‌创建菜单项‌ 在Form的构造函数或加载事件中&#xff0c;‌创建ContextMenuStrip控件的实例&#xff0c;‌并为其添加菜单项。‌ 2.‌绑定到控件‌ 将Con…

tcp 流量控制

TCP流量控制是TCP/IP协议中用于控制发送方和接收方之间数据传输速率的一种机制&#xff0c;以防止网络拥塞和确保网络资源的有效利用。流量控制主要通过调整TCP窗口大小来实现&#xff0c;确保发送方不会发送超出接收方处理能力的数据量。以下是TCP流量控制的关键概念和工作原理…