【为项目做准备】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

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念