理解Linux系统: 进程
Linux内核版本: 2.6.11.12
编写代码: 创建进程
创建进程使用fork系统调用,官方文档对于fork的描述:
fork() creates a new process by duplicating the calling process. The
new process is referred to as the child process. The calling process
is referred to as the parent process.
The child process and the parent process run in separate memory spaces.
At the time of fork() both memory spaces have the same content. Memory
writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed
by one of the processes do not affect the other.
...
fork会创建一个新的进程,通过复制当前调用的进程。这个新的进程被称作子进程,当前调用的进程被称作父进程。子进程和父进程会运行两个不同的内存空间,在fork被调用时,父子进程的地址空间拥有相同的内容(或者叫上下文?),自fork之后父子进程便是隔离的,不相互干扰的。
实例代码 process.c:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
extern int create_process(char *program,char** arg_list);
int create_process(char* program,char** arg_list)
{
pid_t child_pid;
child_pid = fork(); // 进程分叉
if (child_pid) {
return child_pid;
} else {
execvp(program,arg_list); // execute a file
abort(); // cause abnormal process termination
}
}
调用fork之后,父子进程则分道扬镳,通过fork的返回值来判断是哪一个进程,在父进程中fork会返回子进程的ID,在子进程中,fork会返回0。在子进程中使用execvp运行一个新的程序,如下创建第二个文件来调用:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
extern int create_process(char *program,char ** arg_list);
int main(void)
{
char* arg_list[] = {
"ls",
"-l",
};
create_process("ls",arg_list);
return 0;
}
编译: 程序的二进制格式
CPU无法直接执行文本文件中的内容,能够执行的只有二进制指令,因此要将C语言代码编译为二进制程序,二进制程序具有严格的格式,称作ELF,如下是文本文件编译为二进制格式的过程:
如下进行编译:
$ gcc -c -fPIC process.c
$ gcc -c -fPIC main.c
在编译前,先做预处理工作,然后才是真正的编译过程,最终编译为.o文件,这是ELF的第一种类型: 可重定位文件
格式如下:
在源代码的/include/linux/elf.h目录下,定义了两个结构体,对应32位和64位的ELF文件头:
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry; /* Entry point */
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
typedef struct elf64_hdr {
unsigned char e_ident[16]; /* ELF "magic number" */
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
ELF文件头用于描述整个ELF文件
如上图所示,ELF文件是由一个个节(section)组成的
名称 | 含义 |
---|---|
.text | 放置可执行代码 |
.data | 已经初始化的全局变量 |
.rodata | 只读数据 |
.bss | 未初始化的全局变量,运行时置0 |
.symtab | 符号表,记录函数和变量 |
.strtab | 字符串表、字符串常量和变量名 |
程序要运行起来,编译好的代码和变量将会被加载到一定位置。例如调用一个函数时,就是跳到这个函数所在的代码位置执行。但.o文件并不是一个可以直接运行的程序,其中只是部分代码片段。其中的create_process函数,将来会被谁调用,在哪里调用是不清楚的,因此被加载到内存的哪里是不确定的,但必须是可重定位的。可以将一系列.o文件归档为.a静态链接库文件: ar cr libprocess.a process.o
,编译: gcc -o process main.o -L. -lprocess
编译后形成的二进制文件叫可执行文件,是ELF的第二种格式,格式如下:
该文件是可以立即加载到内存运行的文件,其中的section被分为要加载到内存里面的代码段、数据段和无须家早到内存中的部分,将小的section合成了大的段segment,并且在最前面加一个段头表(Segment Header Table)。同样定义在elf.h中:
typedef struct elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
typedef struct elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment, file & memory */
} Elf64_Phdr;
在ELF头中,有一项e_entry,是一个虚拟地址,是这个程序运行的入口
静态链接库一旦被链接,代码和变量的section都被合并,因而程序运行时就不依赖于这个库是否存在。但这样有一个缺点,相同的代码段,如果被多个程序使用,在内存中就有多份,而且一旦静态链接库更新了,如果二进制执行文件不重新编译,也不随着更新。
动态链接库(Shared Libraries),不仅仅是一组对象文件的简单归档,而是多个对象文件的重新组合,可以被多个程序共享,编译动态链接库:gcc -shared -fPIC -o libprocess.so process.o
,编译可执行文件: gcc -o process main.o -L. -lprocess
当一个动态链接库被链接到一个程序文件中,最后程序文件并不包括动态链接库中代码,而仅仅包括对动态链接库的引用,并且不保存动态链接库的全路径,仅仅保存动态链接库的名称。
当运行这个程序时,首先寻找动态链接库,然后加载它。默认情况下,系统在/lib和/usr/lib文件夹下寻找动态链接库,如果找不到就报错,可以设定LD_LIBRARY_PATH环境变量,程序运行时会在此环境变量指定的文件夹下寻找动态链接库
运行程序为进程
运行程序时,要将ELF格式的二进制文件加载到内存中,内核中有这样一个数据结构,用于定义加载二进制文件的方法:
struct linux_binfmt {
struct list_head lh;
struct module *module;
int (*load_binary)(struct linux_binprm *);
int (*load_shlib)(struct file *);
int (*core_dump)(struct coredump_params *cprm);
unsigned long min_coredump; /* minimal dump size */
} __randomize_layout;
对于ELF文件格式有对应的实现:
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
进程树
所有的进程都是从父进程 fork 过来的,就有一个祖宗进程,这就是系统启动的 init 进程。
系统启动之后,init 进程会启动很多的 daemon 进程,为系统运行提供服务,然后就是启动 getty,让用户登录,登录后运行 shell,用户启动的进程都是通过 shell 运行的,从而形成了一棵进程树。可以通过 ps -ef 命令查看当前系统启动的进程,会发现有三类进程:
$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 7月09 ? 00:00:10 /sbin/init splash
root 2 0 0 7月09 ? 00:00:00 [kthreadd]
root 3 2 0 7月09 ? 00:00:00 [rcu_gp]
root 4 2 0 7月09 ? 00:00:00 [rcu_par_gp]
root 6 2 0 7月09 ? 00:00:00 [kworker/0:0H-events_highpri
root 9 2 0 7月09 ? 00:00:00 [mm_percpu_wq]
root 10 2 0 7月09 ? 00:00:00 [rcu_tasks_rude_]
root 11 2 0 7月09 ? 00:00:00 [rcu_tasks_trace]
root 12 2 0 7月09 ? 00:00:00 [ksoftirqd/0]
root 13 2 0 7月09 ? 00:00:22 [rcu_sched]
root 14 2 0 7月09 ? 00:00:00 [migration/0]
root 15 2 0 7月09 ? 00:00:00 [idle_inject/0]
.....
root 127746 2 0 08:07 ? 00:00:00 [kworker/14:1-events]
root 127748 2 0 08:08 ? 00:00:00 [kworker/15:0-events]
root 127774 2 0 08:09 ? 00:00:00 [kworker/u32:2-i915]
hwx 127832 1649 0 08:10 ? 00:00:00 /usr/libexec/tracker-store
root 127862 2 0 08:10 ? 00:00:00 [kworker/u33:1]
root 127876 2 0 08:10 ? 00:00:00 [kworker/12:1-cgroup_destroy
hwx 127922 1649 0 08:11 ? 00:00:00 /usr/libexec/tracker-extract
hwx 127953 1649 6 08:11 ? 00:00:00 /usr/libexec/gnome-terminal-
hwx 127961 127953 0 08:11 pts/0 00:00:00 bash
hwx 127967 127961 0 08:11 pts/0 00:00:00 ps -ef
PID 1 的进程就是 init 进程 systemd,PID 2 的进程是内核线程 kthreadd。其中用户态的不带中括号,内核态的带中括号。
接下来进程号依次增大,但是会看所有带中括号的内核态的进程,祖先都是 2 号进程。而用户态的进程,祖先都是 1 号进程。tty 那一列,是问号的,说明不是前台启动的,一般都是后台的服务。
理解Linux系统: 进程的更多相关文章
- 深入理解linux系统下proc文件系统内容
深入理解linux系统下proc文件系统内容 内容摘要:Linux系统上的/proc目录是一种文件系统,即proc文件系统. Linux系统上的/proc目录是一种文件系统,即proc文件系统.与其它 ...
- 理解Linux系统中的load average
理解Linux系统中的load average(图文版) 博客分类: Linux linux load nagios 一.什么是load average? linux系统中的Load对当前CPU工作 ...
- Linux初学者:从不同角度理解Linux系统
在我初学Linux系统时,虽然已经掌握了一些命令,但总觉得还是很混乱.大家新买的笔记本如果是Windows系统,那么第一件事往往就是分区,目的就是将系统和软件分开.然而Linux却没有类似于Windo ...
- 理解Linux的进程,线程,PID,LWP,TID,TGID
在Linux的top和ps命令中,默认看到最多的是pid (process ID),也许你也能看到lwp (thread ID)和tgid (thread group ID for the threa ...
- 1 理解Linux系统的“平均负载”
什么是平均负载 我们知道使用top或uptime可以用来了解系统的负载情况. uptime 2 02:34:03 up 2 days, 20:14, 1 user, load average: 0.6 ...
- 深入理解 Linux的进程,线程,PID,LWP,TID,TGID
转载:https://www.linuxidc.com/Linux/2019-03/157819.htm 在Linux的top和ps命令中,默认看到最多的是pid (process ID),也许你也能 ...
- 深入理解linux系统的目录结构(总结的非常详细)
对于每一个Linux学习者来说,了解Linux文件系统的目录结构,是学好Linux的至关重要的一步.,深入了解linux文件目录结构的标准和每个目录的详细功能,对于我们用好linux系统只管重要,下面 ...
- 理解Linux系统/etc/init.d目录和/etc/rc.local脚本
一.关于/etc/init.d 如果你使用过Linux系统,那么你一定听说过init.d目录.这个目录到底是干嘛的呢?它归根结底只做了一件事情,但这件事情非同小可,是为整个系统做的,因此它非常重 ...
- [转]理解Linux系统中的load average
转自:http://heipark.iteye.com/blog/1340384 谢谢,写的非常好的文章. 一.什么是load average linux系统中的Load对当前CPU工作量的度量 (W ...
- 理解Linux系统中的load average(图文版)转
一.什么是load average? linux系统中的Load对当前CPU工作量的度量 (WikiPedia: the system load is a measure of the amount ...
随机推荐
- Java的由来
Java 发展史 1.1.起源 20 世纪 90 年代,单片式计算机系统诞生,单片式计算机系统不仅廉价,而且功能强大,使用它 可以大幅度提升消费性电子产品的智能化程度. SUN 公司为了抢占市场先机, ...
- 如何配置php.ini才能让PHP性能最大优化
用于生产环境中的PHP需要对其进行优化,可以让PHP自身发挥更好的性能,除了写好PHP代码,还要配置好php.ini.本文从内存.文件上传.会话缓冲输出.真实路径缓存这几个方面讲解php.ini的配置 ...
- django_设计模式和模板层
一.django的设计模式 1.传统MVC设计模式 (1)MVC(Model-View-Controller,模型-视图-控制器)模式. M--模型层,主要用于对数据库的封装: V--视图层,用于向用 ...
- MacOS 使用UnblockNeteaseMusic解锁网易云灰色歌曲(主要是想听杰伦)
最近想听杰伦的音乐 但是网易云木有版权 于是在github上找到了UnblockNeteaseMusic这个项目 不多废话 直接上教程! 第一步 找到该项目的地址 并使用git克隆到本地: https ...
- MySQL代替in之临时表
如果我们正常的使用IN去查询 SELECT * FROM a JOIN b ON a.id = b.id WHERE b.tag_id IN (1,2,3,4,5,6) 这种因为in里面的参数是连续的 ...
- Think Python 学习笔记
#!/usr/bin/env python# coding: utf-8# # Think Python 学习笔记# 1.关于异或计算符# In[2]:6^2# 2.关于函数# 注意:变量名称不能用数 ...
- centos安装k8s注意点
安装方法,参考 https://blog.csdn.net/frankgy01/article/details/127936367 https://www.cnblogs.com/yangzp/p/1 ...
- python中时间的datatime的模块
datetime.datetime.now().strftime('%Y-%m-%d-%H_%M_%S')1.python datetime模块用strftime 格式化时间 import datet ...
- 关于tomcat部署web服务方式
方式1.apache-tomcat-8.0.47\webapps文件夹下放war包会自动解压.文件夹名称就是访问路径 方式2.apache-tomcat-8.0.47\conf\Catalina\lo ...
- 【picoCTF】GET aHEADwrite up
打开链接,页面如下: 这道题我试了两种解法,大家都可以看看哦! 一.burpsuit拦截 1.点击bule,打开burpsuit拦截(记得打开intercept哦) 2.将 POST 请求更改为 HE ...