做网站标签栏的图片大小,秦皇岛建设网官网,网页设计专业培训班,珠海高端网站开发这两天学校的事情总压着#xff0c;day2拖了好几天..day2内容是进程数据结构 进程数据结构信号处理任务状态进程调度运行统计信息进程亲缘关系进程权限用户和组标识符(IDs)linux capabilities 内存管理文件与文件系统用户态与内核态用户态与内核态的转换函数调用栈内核栈和tas… 这两天学校的事情总压着day2拖了好几天..day2内容是进程数据结构 进程数据结构信号处理任务状态进程调度运行统计信息进程亲缘关系进程权限用户和组标识符(IDs)linux capabilities 内存管理文件与文件系统用户态与内核态用户态与内核态的转换函数调用栈内核栈和task_struct的关系   进程数据结构 
进程or线程在内核中统一叫任务(Task)由一个统一的结构task_struct来管理。 
怎么管理 
首先需要一个链表将所有任务串起来 struct list_head tasks 
接下来具体看看任务包含哪些东西 
任务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结构体分别用于管理与信号相关的状态信息和信号处理函数。blockedreal_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.hint 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.hint 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是parentbash是这个进程的real_parent。 
示例代码 getppid()函数来获取并打印当前进程的父进程ID。这对于理解进程之间的关系以及系统如何管理这些关系非常有用。 
#include stdio.h
#include unistd.hint main() {pid_t ppid  getppid();  // 获取父进程IDprintf(Parent process ID: %d\n, ppid);return 0;
} 
进程权限 
用户和组标识符(IDs) 
实际和有效用户/组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 us program系统调用和权限检查 
示例代码在服务中临时更改权限 
#include sys/types.h
#include unistd.hvoid 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权限分解为更细粒度的权限集允许对特定系统操作进行精细控制。 
重要的能力标识部分 
CAP_CHOWN: 改变文件所有者。CAP_NET_BIND_SERVICE: 绑定到系统端口1024以下。CAP_SYS_BOOT: 允许重启系统。 
能力集合 
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 字段和进程IDpid 字段。这是诊断和监视当前运行环境非常有用的信息。
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.hint 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.hint init_module(void) {printk(KERN_INFO Current process state: %ld\n, current-state);return 0;
}这段代码查看并打印当前进程的状态如运行、睡眠等。state 字段是 task_struct 中用于追踪进程状态的关键成员。