操作系统3

系统调用

打开文件,使用glibc
在这里插入图片描述
系统调用表:
32位的系统调用表定义在arch/x86/entry/syscalls/syscall_32.tbl文件里。例如open是这样定义的:
5 i386 open sys_open compat_sys_open
64位的系统调用定义在另一个文件arch/x86/entry/syscalls/syscall_64.tbl里。例如open是这样定义的:
2 common open sys_open
第一列的数字是系统调用号。可以看出,32位和64位的系统调用号是不一样的。第三列是系统调用的名字,第四列是系统调用在内核的实现函数。不过,它们都是以sys_开头。

用系统调用创建进程

创建进程

   #include 
    #include 
    #include 
    #include 
    
    
    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 != 0)
            return child_pid;
        else {
            execvp (program, arg_list);
            abort ();
        }
   }

调用上面函数

#include 
#include 
#include 
#include 

extern int create_process (char* program, char** arg_list);

int main ()
{
    char* arg_list[] = {
        "ls",
        "-l",
        "/etc/yum.repos.d/",
        NULL
    };
    create_process ("ls", arg_list);
    return 0;
}

编写完程序需要进行编译。linux下二进制程序格式为ELF (可链接格式)
编译这两个程序:
gcc -c -fPIC process.c
gcc -c -fPIC createprocess.c
最终编译成为.o文件,这就是ELF的第一种类型,可重定位文件(Relocatable File)。
在这里插入图片描述
ELF文件的头是用于描述整个文件的。这个文件格式在内核中有定义,分别为struct elf32_hdr和struct elf64_hdr。
.text:放编译好的二进制可执行代码
.data:已经初始化好的全局变量
.rodata:只读数据,例如字符串常量、const的变量
.bss:未初始化全局变量,运行时会置0
.symtab:符号表,记录的则是函数和变量
.strtab:字符串表、字符串常量和变量名
这些节的元数据信息也需要有一个地方保存,就是最后的节头部表(Section Header Table)。在这个表里面,每一个section都有一项,在代码里面也有定义struct elf32_shdr和struct elf64_shdr。在ELF的头里面,有描述这个文件的节头部表的位置,有多少个表项等等信息。
要想让create_process这个函数作为库文件被重用,不能以.o的形式存在,而是要形成库文件,最简单的类型是静态链接库.a文件(Archives),仅仅将一系列对象文件(.o)归档为一个文件,使用命令ar创建。
ar cr libstaticprocess.a process.o
虽然这里libstaticprocess.a里面只有一个.o,但是实际情况可以有多个.o。当有程序要使用这个静态连接库的时候,会将.o文件提取出来,链接到程序中。
gcc -o staticcreateprocess createprocess.o -L. -lstaticprocess
在这个命令里,-L表示在当前目录下找.a文件,-lstaticprocess会自动补全文件名,比如加前缀lib,后缀.a,变成libstaticprocess.a,找到这个.a文件后,将里面的process.o取出来,和createprocess.o做一个链接,形成二进制执行文件staticcreateprocess。
这个链接的过程,重定位就起作用了,原来createprocess.o里面调用了create_process函数,但是不能确定位置,现在将process.o合并了进来,就知道位置了。
形成的二进制文件叫可执行文件,是ELF的第二种格式,格式如下:
在这里插入图片描述
这个格式和.o文件大致相似,还是分成一个个的section,并且被节头表描述。只不过这些section是多个.o文件合并过的。但是这个时候,这个文件已经是马上就可以加载到内存里面执行的文件了,因而这些section被分成了需要加载到内存里面的代码段、数据段和不需要加载到内存里面的部分,将小的section合成了大的段segment,并且在最前面加一个段头表(Segment Header Table)。
静态链接库一旦链接进去,代码和变量的section都合并了,因而程序运行的时候,就不依赖于这个库是否存在。但是这样有一个缺点,就是相同的代码段,如果被多个程序使用的话,在内存里面就有多份,而且一旦静态链接库更新了,如果二进制执行文件不重新编译,也不随着更新。
动态链接库(Shared Libraries)
gcc -shared -fPIC -o libdynamicprocess.so process.o
当一个动态链接库被链接到一个程序文件中的时候,最后的程序文件并不包括动态链接库中的代码,而仅仅包括对动态链接库的引用,并且不保存动态链接库的全路径,仅仅保存动态链接库的名称。
gcc -o dynamiccreateprocess createprocess.o -L. -ldynamicprocess
动态链接库,就是ELF的第三种类型,共享对象文件(Shared Object)。
基于动态链接库创建出来的二进制文件格式还是ELF,但是稍有不同。
首先,多了一个.interp的Segment,这里面是ld-linux.so,这是动态链接器,也就是说,运行时的链接动作都是它做的。
另外,ELF文件中还多了两个section,一个是.plt,过程链接表(Procedure Linkage Table,PLT),一个是.got.plt,全局偏移量表(Global Offset Table,GOT)。

进程树
在这里插入图片描述
其中1号进程被软链接到systemd
ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 2018 ? 00:00:29 /usr/lib/systemd/systemd --system --deserialize 21
root 2 0 0 2018 ? 00:00:00 [kthreadd]
root 3 2 0 2018 ? 00:00:00 [ksoftirqd/0]
root 5 2 0 2018 ? 00:00:00 [kworker/0:0H]
root 9 2 0 2018 ? 00:00:40 [rcu_sched]

root 337 2 0 2018 ? 00:00:01 [kworker/3:1H]
root 380 1 0 2018 ? 00:00:00 /usr/lib/systemd/systemd-udevd
root 415 1 0 2018 ? 00:00:01 /sbin/auditd
root 498 1 0 2018 ? 00:00:03 /usr/lib/systemd/systemd-logind

root 852 1 0 2018 ? 00:06:25 /usr/sbin/rsyslogd -n
root 2580 1 0 2018 ? 00:00:00 /usr/sbin/sshd -D
root 29058 2 0 Jan03 ? 00:00:01 [kworker/1:2]
root 29672 2 0 Jan04 ? 00:00:09 [kworker/2:1]
root 30467 1 0 Jan06 ? 00:00:00 /usr/sbin/crond -n
root 31574 2 0 Jan08 ? 00:00:01 [kworker/u128:2]

root 32792 2580 0 Jan10 ? 00:00:00 sshd: root@pts/0
root 32794 32792 0 Jan10 pts/0 00:00:00 -bash
root 32901 32794 0 00:01 pts/0 00:00:00 ps -ef
所有用户态的进程的父进程都是systemd 1号进程,所有内核态进程祖先都是2号进程
tty为?的说明不是前台启动的,一般是后台服务。而带有中括号的表明是内核进程。
小结:
通过编译生成so文件和可执行文件。下图左边的用户态的进程A执行fork,创建进程B,在进程B的处理逻辑中,执行exec系列系统调用。这个系统调用会通过load_elf_binary方法,将刚才生成的可执行文件,加载到进程B的内存中执行。
在这里插入图片描述

线程

线程能够并行执行从而提升整体执行的效率。
为什么不使用进程并发执行呢?
第一,创建进程占用资源太多;第二,进程之间的通信需要数据在不同的内存空间传来传去,无法共享。
线程中的数据:
1、线程栈的私有数据
栈的大小可以通过命令ulimit -a查看,默认情况下线程栈大小为8192(8MB)。我们可以使用命令ulimit -s修改。
主线程在内存中有一个栈空间,其他线程栈也拥有独立的栈空间。为了避免线程之间的栈空间踩踏,线程栈之间还会有小块区域,用来隔离保护各自的栈空间。一旦另一个线程踏入到这个隔离区,就会引发段错误。
2、在整个进程里共享的全局数据。
共享变量的修改需要加锁。
a、Mutex,全称Mutual Exclusion,中文叫互斥。
3、线程私有数据。类似于ThreadLocal
int pthread_key_create(pthread_key_t *key, void (destructor)(void))
通过这个函数创建
总结:
在这里插入图片描述
#内存空间
每个项目的物理地址对于进程不可见,谁也不能直接访问这个物理地址。操作系统会给进程分配一个虚拟地址。所有进程看到的这个地址都是一样的,里面的内存都是从0开始编号。
在程序里面,指令写入的地址是虚拟地址。例如,位置为10M的内存区域,操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。
当程序要访问虚拟地址的时候,由内核的数据结构进行转换,转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。
内存管理:
1、物理内存管理。
2、虚拟地址管理。
3、映射列表管理。
虚拟空间一切二,一部分用来放内核的东西,称为内核空间,一部分用来放进程的东西,称为用户空间。用户空间在下,在低地址;内核空间在上,在高地址。
![在这里插入图片描述](https://img-blog.csdnimg.cn/d67975ee65564006954aaff43055d277.png
Text Segment是存放二进制可执行代码的位置,Data Segment存放静态常量,BSS Segment存放未初始化的静态变量。是不是觉得这几个名字很熟悉?没错,咱们前面讲ELF格式的时候提到过,在二进制执行文件里面,就有这三个部分。这里就是把二进制执行文件的三个部分加载到内存里面。
接下来是堆(Heap)段。堆是往高地址增长的,是用来动态分配内存的区域,malloc就是在这里面分配的。
接下来的区域是Memory Mapping Segment。这块地址可以用来把文件映射进内存用的,如果二进制的执行文件依赖于某个动态链接库,就是在这个区域里面将so文件映射到了内存中。
再下面就是栈(Stack)地址段。主线程的函数调用的函数栈就是用这里的。
如何将虚拟地址映射为真正的物理地址?
在这里插入图片描述
对于物理内存,操作系统把它分成一块一块大小相同的页,这样更方便管理,例如有的内存页面长时间不用了,可以暂时写到硬盘上,称为换出。一旦需要的时候,再加载进来,叫做换入。这样可以扩大可用物理内存的大小,提高物理内存的利用率。
这个换入和换出都是以页为单位的。页面的大小一般为4KB。为了能够定位和访问每个页,需要有个页表,保存每个页的起始地址,再加上在页内的偏移量,组成线性地址,就能对于内存中的每个位置进行访问了。
在这里插入图片描述
虚拟地址分为两部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址。这个基地址与页内偏移的组合就形成了物理内存地址。
对于32位系统为了省空间,抽象出页目录。
在这里插入图片描述
对于64位系统有4级四级目录,分别是全局页目录项PGD(Page Global Directory)、上层页目录项PUD(Page Upper Directory)、中间页目录项PMD(Page Middle Directory)和页表项PTE(Page Table Entry)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值