理解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 ...
随机推荐
- PHP实现斐波那契数列(递归 + 非递归)实现
非递归写法:function fbnq($n){ //传入数列中数字的个数 if($n <= 0){ return 0; } $array[1] = $array ...
- css3样式pointer-events,点击穿透和海市蜃楼的效果
css样式pointer-events pointer-events 是CSS3的一个属性,支持的值非常多,其中大部分都是和SVG有关.目前只了解 none 这个值, 其他值后续要补上. pointe ...
- win10启动和安装nacos服务
https://blog.csdn.net/tbmingzhao/article/details/113276845
- jquery的ajax方法获取不到return返回值
/** 2 * 方式:(1)同步调用 (2)在ajax函数中return值 3 * 结果:返回 1.未成功获取返回值 4 * 失败原因:ajax内部是一个或多个定义的函数,ajax中return返回值 ...
- C语言中字符数组的赋值和复制
/*C中,字符串,即字符数组的赋值与字符变量.常量.变量的赋值是不同的.初学者总会犯错误. 常见错误如下: 1.定义的时候直接用字符串赋值 char a[10]; char a[10]="h ...
- ios装包
一.下载爱思助手 二.找到本机设备 注:如果未弹出允许.拒绝调试选项可尝试换根数据线解决 三.将对应包体文件拖入本机设备
- 实验七:基于REST API的SDN北向应用实践
(一)基本要求 编写Python程序,调用OpenDaylight的北向接口实现以下功能 (1) 利用Mininet平台搭建下图所示网络拓扑,并连接OpenDaylight: (2) 下发指令删除s1 ...
- 谷歌翻译不能用解决办法(谷歌翻译关闭后,如何继续使用Chrome浏览器的翻译功能?)
1.查找 IP 虽然谷歌不再提供 translate.google.cn 网页版的服务了,但谷歌翻译的 API 服务还在. 只需要通过 hosts 重定向至国内服务器,即可恢复使用. 1.Ping ...
- VSCODE C# 运行 找不到任务"BUILD"----C#常用命令
使用 Visual Studio Code 创建 .NET 类库 - .NET | Microsoft Docs 安装vscode.vscode c#相关拓展.MINIGW64 1.创建文件夹 2.用 ...
- Keil Jlink没法找到STM32H750
https://www.amobbs.com/thread-5713382-1-1.html MDK使用的是5.32,jlink使用的是9.2jlink驱动使用的是6.44b 删除工程下的JLinkS ...