《趣谈Linux操作系统》操作系统综述——小记随笔BlueMountain

操作系统其实就像一个软件外包公司,其内核就相当于这家外包公司的老板。所以接下来的整个课程中,请你将自己的角色切换成这家软件外包公司的老板,设身处地地去理解操作系统是如何协调各种资源,帮客户做成事情的。

趣谈操作系统组成,类比至外包软件公司

操作系统全貌概览

#修改密码passwd#添加用户useradd#查询用户、用户组cat/etc/passwdcat/etc/group浏览文件#ls-ldrwxr-xr-x6rootroot4096Oct202017apt-rw-r--r--1rootroot211Oct202017hosts你可以通过命令chown改变所属用户,chgrp改变所属组。

临时生效

exportJAVA_HOME=/root/jdk-XXX_linux-x64exportPATH=$JAVA_HOME/bin:$PATH永久生效,在用户的.bashrc文件中加上这两行

./filenamenohupcommand>out.file2>&1&这个时候,我们往往使用nohup命令。这个命令的意思是nohangup(不挂起),也就是说,当前交互命令行退出的时候,程序还要在。

这里面,“1”表示文件描述符1,表示标准输出,“2”表示文件描述符2,意思是标准错误输出,“2>&1”表示标准输出和错误输出合并了。合并到哪里去呢?到out.file里。

例如在Ubuntu中,我们可以通过apt-getinstallmysql-server的方式安装MySQL,然后通过命令systemctlstartmysql启动MySQL,通过systemctlenablemysql设置开机启动。之所以成为服务并且能够开机启动,是因为在/lib/systemd/system目录下会创建一个XXX.service的配置文件,里面定义了如何启动、如何关闭。

创建进程调用叫fork,因为实现上是fork一个老的进程来创建新进程。老的叫父进程,新的子进程。

调用fork时候,子进程把父进程完完整整copy了一份,包括数据+代码,所以如果没有特殊处理,那父子进程就会按照一样代码执行下去了。

所以fork函数会根据场景提供返回值

代码需要判定返回值,如果是0执行系统嗲用execve执行另外一段程序代码。否则按照原样执行。

对于操作系统也一样,启动的时候先创建一个所有用户进程的“祖宗进程”。

有时候,父进程要关心子进程的运行情况,这毕竟是自己身上掉下来的肉。有个系统调用waitpid,父进程可以调用它,将子进程的进程号作为参数传给它,这样父进程就知道子进程运行完了没有,成功与否。

每个进程都有自己的内存,互相之间不干扰,有独立的进程内存空间。进程内存有啥内容

分配内存时是按需分配,需要时才分配

对于文件的操作,下面6个系统调用最重要

Linux系统一切皆文件

每个文件,Linux都会分配一个文件描述符(FileDescriptor),这是一个整数。有了这个文件描述符,我们就可以使用系统调用,查看或者干预进程运行的方方面面。

所以说,文件操作是贯穿始终的,这也是“一切皆文件”的优势,就是统一了操作的入口,提供了极大的便利。

当项目遇到异常情况,例如项目中断,做到一半不做了。这时候就需要发送一个信号(Signal)给项目组。经常遇到的信号有以下几种:

当项目组收到信号的时候,项目组需要决定如何处理这些异常情况。对于一些不严重的信号,可以忽略,该干啥干啥,但是像SIGKILL(用于终止一个进程的信号)和SIGSTOP(用于中止一个进程的信号)是不能忽略的,可以执行对于该信号的默认动作。每种信号都定义了默认的动作,例如硬件故障,默认终止;也可以提供信号处理函数,可以通过sigaction系统调用,注册一个信号处理函数。

首先就是发个消息,不需要一段很长的数据,这种方式称为消息队列(MessageQueue)。由于一个公司内的多个项目组沟通时,这个消息队列是在内核里的,我们可以通过msgget创建一个新的队列,msgsnd将消息发送到消息队列,而消息接收方可以使用msgrcv从队列中取消息。

当两个项目组需要交互的信息比较大的时候,可以使用共享内存的方式,也即两个项目组共享一个会议室(这样数据就不需要拷贝来拷贝去)。大家都到这个会议室来,就可以完成沟通了。这时候,我们可以通过shmget创建一个共享内存块,通过shmat将共享内存映射到自己的内存空间,然后就可以读写了。

但是,两个项目组共同访问一个会议室里的数据,就会存在“竞争”的问题。如果大家同时修改同一块数据咋办?这就需要有一种方式,让不同的人能够排他地访问,这就是信号量的机制Semaphore。

不同机器的通过网络相互通信,要遵循相同的网络协议,也即TCP/IP网络协议栈。

。Linux内核里有对于网络协议栈的实现。如何暴露出服务给项目组使用呢?网络服务是通过套接字Socket来提供服务的。

我们可以通过Socket系统调用建立一个Socket。Socket也是一个文件,也有一个文件描述符,也可以通过读写函数进行通信。

如果你做过开发,你会觉得刚才讲的和平时咱们调用的函数不太一样。这是因为,平时你并没有直接使用系统调用。虽然咱们的办事大厅已经很方便了,但是为了对用户更友好,我们还可以使用中介Glibc,有事情找它就行,它会转换成为系统调用,帮你调用。Glibc是Linux下使用的开源的标准C库,它是GNU发布的libc库。Glibc为程序员提供丰富的API,除了例如字符串处理、数学运算等用户态服务之外,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。

yum-ygroupinstall"DevelopmentTools"#include#include#include#includeexternintcreate_process(char*program,char**arg_list);intcreate_process(char*program,char**arg_list){pid_tchild_pid;child_pid=fork();if(child_pid!=0)returnchild_pid;else{execvp(program,arg_list);abort();}}#include#include#include#includeexternintcreate_process(char*program,char**arg_list);intmain(){char*arg_list[]={"ls","-l","/etc/yum.repos.d/",NULL};create_process("ls",arg_list);return0;}在这里,我们创建的子程序运行了一个最最简单的命令ls。

在Linux下面,二进制的程序也要有严格的格式,这个格式我们称为ELF(ExecuteableandLinkableFormat,可执行与可链接格式)

gcc-c-fPICprocess.cgcc-c-fPICcreateprocess.cELF的第一种类型,可重定位文件(RelocatableFile)在编译的时候,先做预处理工作,例如将头文件嵌入到正文中,将定义的宏展开,然后就是真正的编译过程,最终编译成为.o文件,这就是ELF的第一种类型,可重定位文件(RelocatableFile)。

ELF文件的头是用于描述整个文件的。这个文件格式在内核中有定义,分别为structelf32_hdr和structelf64_hdr。

接下来我们来看一个一个的section,我们也叫节。

这些节的元数据信息也需要有一个地方保存,就是最后的节头部表(SectionHeaderTable)。在这个表里面,每一个section都有一项,在代码里面也有定义structelf32_shdr和structelf64_shdr。在ELF的头里面,有描述这个文件的节头部表的位置,有多少个表项等等信息。

多个文件互相调用,各个文件内的代码偏移会冲突,需要重定位。.rel.text,.rel.data就与重定位有关。

要想让create_process这个函数作为库文件被重用,不能以.o的形式存在,而是要形成库文件,最简单的类型是静态链接库.a文件(Archives),仅仅将一系列对象文件(.o)归档为一个文件,使用命令ar创建。

arcrlibstaticprocess.aprocess.o虽然这里libstaticprocess.a里面只有一个.o,但是实际情况可以有多个.o。当有程序要使用这个静态连接库的时候,会将.o文件提取出来,链接到程序中。

gcc-ostaticcreateprocesscreateprocess.o-L.-lstaticprocess在这个命令里,-L表示在当前目录下找.a文件,-lstaticprocess会自动补全文件名,比如加前缀lib,后缀.a,变成libstaticprocess.a,找到这个.a文件后,将里面的process.o取出来,和createprocess.o做一个链接,形成二进制执行文件staticcreateprocess。

这个链接的过程,重定位就起作用了,原来createprocess.o里面调用了create_process函数,但是不能确定位置,现在将process.o合并了进来,就知道位置了。

这个格式和.o文件大致相似,还是分成一个个的section,并且被节头表描述。只不过这些section是多个.o文件合并过的。但是这个时候,这个文件已经是马上就可以加载到内存里面执行的文件了,因而这些section被分成了需要加载到内存里面的代码段、数据段和不需要加载到内存里面的部分,将小的section合成了大的段segment,

并且在最前面加一个段头表(SegmentHeaderTable)。在代码里面的定义为structelf32_phdr和structelf64_phdr,这里面除了有对于段的描述之外,最重要的是p_vaddr,这个是这个段加载到内存的虚拟地址。

在ELF头里面,有一项e_entry,也是个虚拟地址,是这个程序运行的入口。

#exportLD_LIBRARY_PATH=.#./dynamiccreateprocess#total40-rw-r--r--.1rootroot1572Oct2418:38CentOS-Base.repo......ELF的第三种类型,共享对象文件(SharedObject)。静态链接库一旦链接进去,代码和变量的section都合并了,因而程序运行的时候,就不依赖于这个库是否存在。但是这样有一个缺点,就是相同的代码段,如果被多个程序使用的话,在内存里面就有多份,而且一旦静态链接库更新了,如果二进制执行文件不重新编译,也不随着更新。

因而就出现了另一种,动态链接库(SharedLibraries),不仅仅是一组对象文件的简单归档,而是多个对象文件的重新组合,可被多个程序共享。

gcc-shared-fPIC-olibdynamicprocess.soprocess.o当一个动态链接库被链接到一个程序文件中的时候,最后的程序文件并不包括动态链接库中的代码,而仅仅包括对动态链接库的引用,并且不保存动态链接库的全路径,仅仅保存动态链接库的名称。

gcc-odynamiccreateprocesscreateprocess.o-L.-ldynamicprocess当运行这个程序的时候,首先寻找动态链接库,然后加载它。默认情况下,系统在/lib和/usr/lib文件夹下寻找动态链接库。如果找不到就会报错,我们可以设定LD_LIBRARY_PATH环境变量,程序运行时会在此环境变量指定的文件夹下寻找动态链接库。

#exportLD_LIBRARY_PATH=.#./dynamiccreateprocess#total40-rw-r--r--.1rootroot1572Oct2418:38CentOS-Base.repo......基于动态链接库创建出来的二进制文件格式还是ELF,但是稍有不同。首先,多了一个.interp的Segment,这里面是ld-linux.so,这是动态链接器,也就是说,运行时的链接动作都是它做的。

另外,ELF文件中还多了两个section,一个是.plt,过程链接表(ProcedureLinkageTable,PLT),一个是.got.plt,全局偏移量表(GlobalOffsetTable,GOT)。

dynamiccreateprocess这个程序要调用libdynamicprocess.so里的create_process函数。由于是运行时才去找,编译的时候,压根不知道这个函数在哪里,所以就在PLT里面建立一项PLT[x]。这一项也是一些代码,有点像一个本地的代理,在二进制程序里面,不直接调用create_process函数,而是调用PLT[x]里面的代理代码,这个代理代码会在运行的时候找真正的create_process函数。

去哪里找代理代码呢?这就用到了GOT,这里面也会为create_process函数创建一项GOT[y]。这一项是运行时create_process函数在内存中真正的地址。

如果这个地址在dynamiccreateprocess调用PLT[x]里面的代理代码,代理代码调用GOT表中对应项GOT[y],调用的就是加载到内存中的libdynamicprocess.so里面的create_process函数了。

但是GOT怎么知道的呢?对于create_process函数,GOT一开始就会创建一项GOT[y],但是这里面没有真正的地址,因为它也不知道,但是它有办法,它又回调PLT,告诉它,你里面的代理代码来找我要create_process函数的真实地址,我不知道,你想想办法吧。

PLT这个时候会转而调用PLT[0],也即第一项,PLT[0]转而调用GOT[2],这里面是ld-linux.so的入口函数,这个函数会找到加载到内存中的libdynamicprocess.so里面的create_process函数的地址,然后把这个地址放在GOT[y]里面。下次,PLT[x]的代理函数就能够直接调用了。

知道了ELF这个格式,这个时候它还是个程序,那怎么把这个文件加载到内存里面呢?在内核中,有这样一个数据结构,用来定义加载二进制文件的方法。

structlinux_binfmt{structlist_headlh;structmodule*module;int(*load_binary)(structlinux_binprm*);int(*load_shlib)(structfile*);int(*core_dump)(structcoredump_params*cprm);unsignedlongmin_coredump;/*minimaldumpsize*/}__randomize_layout;对于ELF文件格式,有对应的实现。

staticstructlinux_binfmtelf_format={.module=THIS_MODULE,.load_binary=load_elf_binary,.load_shlib=load_elf_library,.core_dump=elf_core_dump,.min_coredump=ELF_EXEC_PAGESIZE,};load_elf_binary是不是你很熟悉?没错,我们加载内核镜像的时候,用的也是这种格式。

还记得当时是谁调用的load_elf_binary函数吗?具体是这样的:do_execve->do_execveat_common->exec_binprm->search_binary_handler。

那do_execve又是被谁调用的呢?我们看下面的代码。

SYSCALL_DEFINE3(execve,constchar__user*,filename,constchar__user*const__user*,argv,constchar__user*const__user*,envp){returndo_execve(getname(filename),argv,envp);}学过了系统调用一节,你会发现,原理是exec这个系统调用最终调用的load_elf_binary。

exec比较特殊,它是一组函数:

在上面process.c的代码中,我们创建ls进程,也是通过exec。

既然所有的进程都是从父进程fork过来的,那总归有一个祖宗进程,这就是咱们系统启动的init进程。

在解析Linux的启动过程的时候,1号进程是/sbin/init。如果在centOS7里面,我们ls一下,可以看到,这个进程是被软链接到systemd的。

#ps-ef[root@deployer~]#ps-efUIDPIDPPIDCSTIMETTYTIMECMDroot100201800:00:29/usr/lib/systemd/systemd--system--deserialize21root200201800:00:00[kthreadd]root320201800:00:00[ksoftirqd/0]root520201800:00:00[kworker/0:0H]root920201800:00:40[rcu_sched]......root33720201800:00:01[kworker/3:1H]root38010201800:00:00/usr/lib/systemd/systemd-udevdroot41510201800:00:01/sbin/auditdroot49810201800:00:03/usr/lib/systemd/systemd-logind......root85210201800:06:25/usr/sbin/rsyslogd-nroot258010201800:00:00/usr/sbin/sshd-Droot2905820Jan0300:00:01[kworker/1:2]root2967220Jan0400:00:09[kworker/2:1]root3046710Jan0600:00:00/usr/sbin/crond-nroot3157420Jan0800:00:01[kworker/u128:2]......root3279225800Jan1000:00:00sshd:root@pts/0root32794327920Jan10pts/000:00:00-bashroot3290132794000:01pts/000:00:00ps-ef命令查看当前系统启动的进程,我们会发现有三类进程。

你会发现,PID1的进程就是我们的init进程systemd,PID2的进程是内核线程kthreadd,这两个我们在内核启动的时候都见过。其中用户态的不带中括号,内核态的带中括号。

接下来进程号依次增大,但是你会看所有带中括号的内核态的进程,祖先都是2号进程。而用户态的进程,祖先都是1号进程。tty那一列,是问号的,说明不是前台启动的,一般都是后台的服务。

pts的父进程是sshd,bash的父进程是pts,ps-ef这个命令的父进程是bash。这样整个链条都比较清晰了。

对于任何一个进程来讲,即便我们没有主动去创建线程,进程也是默认有一个主线程的。线程是负责执行二进制指令的,它会根据项目执行计划书,一行一行执行下去。进程要比线程管的宽多了,除了执行指令之外,内存、文件系统等等都要它来管。

使用进程实现并行执行的问题也有两个。

在Linux中,有时候我们希望将前台的任务和后台的任务分开。因为有些任务是需要马上返回结果的,例如你输入了一个字符,不可能五分钟再显示出来;而有些任务是可以默默执行的,例如将本机的数据同步到服务器上去,这个就没刚才那么着急。因此这样两个任务就应该在不同的线程处理,以保证互不耽误。

gccdownload.c-lpthread这里我们画一张图总结一下,一个普通线程的创建和运行过程。

我们把线程访问的数据细分成三类:

栈的大小可以通过命令ulimit-a查看,默认情况下线程栈大小为8192(8MB)。我们可以使用命令ulimit-s修改。

intpthread_key_create(pthread_key_t*key,void(*destructor)(void*))可以看到,创建一个key,伴随着一个析构函数。key一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。

intpthread_setspecific(pthread_key_tkey,constvoid*value)void*pthread_getspecific(pthread_key_tkey)而等到线程退出的时候,就会调用析构函数释放value。

我们先来看一种方式,Mutex,全称MutualExclusion,中文叫互斥。顾名思义,有你没我,有我没你。它的模式就是在共享数据访问的时候,去申请加把锁,谁先拿到锁,谁就拿到了访问权限,其他人就只好在门外等着,等这个人访问结束,把锁打开,其他人再去争夺,还是遵循谁先拿到谁访问。

gccmutex.c-lpthread使用Mutex,首先要使用pthread_mutex_init函数初始化这个mutex,初始化后,就可以用它来保护共享变量了。pthread_mutex_lock()就是去抢那把锁的函数,如果抢到了,就可以执行下一行程序,对共享变量进行访问;如果没抢到,就被阻塞在那里等待。

如果不想被阻塞,可以使用pthread_mutex_trylock去抢那把锁,如果抢到了,就可以执行下一行程序,对共享变量进行访问;如果没抢到,不会被阻塞,而是返回一个错误码。

当共享数据访问结束了,别忘了使用pthread_mutex_unlock释放锁,让给其他人使用,最终调用pthread_mutex_destroy销毁掉这把锁。

但是当它接到了通知,来操作共享资源的时候,还是需要抢互斥锁,因为可能很多人都受到了通知,都来访问了,所以条件变量和互斥锁是配合使用的。

写多线程的程序是有套路的,我这里用一张图进行总结。你需要记住的是,创建线程的套路、mutex使用的套路、条件变量使用的套路。

在Linux里面,无论是进程,还是线程,到了内核里面,我们统一都叫任务(Task),由一个统一的结构task_struct进行管理。

设想一下,Linux的任务管理都应该干些啥?首先,所有执行的项目应该有个项目列表吧,所以Linux内核也应该先弄一个链表,将所有的task_struct串起来。

structlist_headtasks;任务IDpid_tpid;pid_ttgid;structtask_struct*group_leader;pid是processid,tgid是threadgroupID。

任何一个进程,如果只有主线程,那pid是自己,tgid是自己,group_leader指向的还是自己。但是,如果一个进程创建了其他线程,那就会有所变化了。线程有自己的pid,tgid就是进程的主线程的pid,group_leader指向的就是进程的主线程。

这里既然提到了下发指令的问题,我就顺便提一下task_struct里面关于信号处理的字段。

/*Signalhandlers:*/structsignal_struct*signal;structsighand_struct*sighand;sigset_tblocked;sigset_treal_blocked;sigset_tsaved_sigmask;structsigpendingpending;unsignedlongsas_ss_sp;size_tsas_ss_size;unsignedintsas_ss_flags;这里定义了哪些信号被阻塞暂不处理(blocked),哪些信号尚等待处理(pending),哪些信号正在通过信号处理函数进行处理(sighand)。处理的结果可以是忽略,可以是结束进程等等。

信号处理函数默认使用用户态的函数栈,当然也可以开辟新的栈专门用于信号处理,这就是sas_ss_xxx这三个变量的作用。

上面我说了下发信号的时候,需要区分进程和线程。从这里我们其实也能看出一些端倪。task_struct里面有一个structsigpendingpending。如果我们进入structsignal_struct*signal去看的话,还有一个structsigpendingshared_pending。它们一个是本任务的,一个是线程组共享的。

关于信号,你暂时了解到这里就够用了,后面我们会有单独的章节进行解读。

在task_struct里面,涉及任务状态的是下面这几个变量:

volatilelongstate;/*-1unrunnable,0runnable,>0stopped*/intexit_state;unsignedintflags;state(状态)可以取的值定义在include/linux/sched.h头文件中。

/*Usedintsk->state:*/#defineTASK_RUNNING0#defineTASK_INTERRUPTIBLE1#defineTASK_UNINTERRUPTIBLE2#define__TASK_STOPPED4#define__TASK_TRACED8/*Usedintsk->exit_state:*/#defineEXIT_DEAD16#defineEXIT_ZOMBIE32#defineEXIT_TRACE(EXIT_ZOMBIE|EXIT_DEAD)/*Usedintsk->stateagain:*/#defineTASK_DEAD64#defineTASK_WAKEKILL128#defineTASK_WAKING256#defineTASK_PARKED512#defineTASK_NOLOAD1024#defineTASK_NEW2048#defineTASK_STATE_MAX4096从定义的数值很容易看出来,state是通过bitset的方式设置的,也就是说,当前是什么状态,哪一位就置一。

在运行中的进程,一旦要进行一些I/O操作,需要等待I/O完毕,这个时候会释放CPU,进入睡眠状态。在Linux中,有两种睡眠状态。

因此,这其实是一个比较危险的事情,除非程序员极其有把握,不然还是不要设置成TASK_UNINTERRUPTIBLE。于是,我们就有了一种新的进程睡眠状态,TASK_KILLABLE,可以终止的新睡眠状态。进程处于这种状态中,它的运行原理类似TASK_UNINTERRUPTIBLE,只不过可以响应致命信号。

TASK_STOPPED是在进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后进入该状态。

TASK_TRACED表示进程被debugger等进程监视,进程执行被调试程序所停止。当一个进程被另外的进程所监视,每一个信号都会让进程进入该状态。

一旦一个进程要结束,先进入的是EXIT_ZOMBIE状态,但是这个时候它的父进程还没有使用wait()等系统调用来获知它的终止信息,此时进程就成了僵尸进程。

EXIT_DEAD是进程的最终状态。

EXIT_ZOMBIE和EXIT_DEAD也可以用于exit_state。

上面的进程状态和进程的运行、调度有关系,还有其他的一些状态,我们称为标志。放在flags字段中,这些字段都被定义成为宏,以PF开头。我这里举几个例子。

#definePF_EXITING0x00000004#definePF_VCPU0x00000010#definePF_FORKNOEXEC0x00000040PF_EXITING表示正在退出。当有这个flag的时候,在函数find_alive_thread中,找活着的线程,遇到有这个flag的,就直接跳过。

PF_FORKNOEXEC表示fork完了,还没有exec。在_do_fork函数里面调用copy_process,这个时候把flag设置为PF_FORKNOEXEC。当exec中调用了load_elf_binary的时候,又把这个flag去掉。

进程的状态切换往往涉及调度,下面这些字段都是用于调度的。为了让你理解task_struct进程管理的全貌,我先在这里列一下

在Linux里面,对于进程权限的定义如下:

/*Objectiveandrealsubjectivetaskcredentials(COW):*/conststructcred__rcu*real_cred;/*Effective(overridable)subjectivetaskcredentials(COW):*/conststructcred__rcu*cred;这个结构的注释里,有两个名词比较拗口,Objective和Subjective。事实上,所谓的权限,就是我能操纵谁,谁能操纵我。“谁能操作我”,很显然,这个时候我就是被操作的对象,就是Objective,那个想操作我的就是Subjective。“我能操作谁”,这个时候我就是Subjective,那个要被我操作的就是Objectvie。

real_cred就是说明谁能操作我这个进程,而cred就是说明我这个进程能够操作谁。

structcred{......kuid_tuid;/*realUIDofthetask*/kgid_tgid;/*realGIDofthetask*/kuid_tsuid;/*savedUIDofthetask*/kgid_tsgid;/*savedGIDofthetask*/kuid_teuid;/*effectiveUIDofthetask*/kgid_tegid;/*effectiveGIDofthetask*/kuid_tfsuid;/*UIDforVFSops*/kgid_tfsgid;/*GIDforVFSops*/......kernel_cap_tcap_inheritable;/*capsourchildrencaninherit*/kernel_cap_tcap_permitted;/*capswe'repermitted*/kernel_cap_tcap_effective;/*capswecanactuallyuse*/kernel_cap_tcap_bset;/*capabilityboundingset*/kernel_cap_tcap_ambient;/*Ambientcapabilityset*/......}__randomize_layout;从这里的定义可以看出,大部分是关于用户和用户所属的用户组信息。

第一个是uid和gid,注释是realuser/groupid。一般情况下,谁启动的进程,就是谁的ID。但是权限审核的时候,往往不比较这两个,也就是说不大起作用。

第二个是euid和egid,注释是effectiveuser/groupid。一看这个名字,就知道这个是起“作用”的。当这个进程要操作消息队列、共享内存、信号量等对象的时候,其实就是在比较这个用户和组是否有权限。

第三个是fsuid和fsgid,也就是filesystemuser/groupid。这个是对文件操作会审核的权限。

一般说来,fsuid、euid,和uid是一样的,fsgid、egid,和gid也是一样的。因为谁启动的进程,就应该审核启动的用户到底有没有这个权限。

但是也有特殊的情况。

例如,用户A想玩一个游戏,这个游戏的程序是用户B安装的。游戏这个程序文件的权限为rwxr–r--。A是没有权限运行这个程序的,所以用户B要给用户A权限才行。用户B说没问题,都是朋友嘛,于是用户B就给这个程序设定了所有的用户都能执行的权限rwxr-xr-x,说兄弟你玩吧。

于是,用户A就获得了运行这个游戏的权限。当游戏运行起来之后,游戏进程的uid、euid、fsuid都是用户A。看起来没有问题,玩得很开心。用户A好不容易通过一关,想保留通关数据的时候,发现坏了,这个游戏的玩家数据是保存在另一个文件里面的。这个文件权限rw-------,只给用户B开了写入权限,而游戏进程的euid和fsuid都是用户A,当然写不进去了。完了,这一局白玩儿了。

那怎么解决这个问题呢?我们可以通过chmodu+sprogram命令,给这个游戏程序设置set-user-ID的标识位,把游戏的权限变成rwsr-xr-x。这个时候,用户A再启动这个游戏的时候,创建的进程uid当然还是用户A,但是euid和fsuid就不是用户A了,因为看到了set-user-id标识,就改为文件的所有者的ID,也就是说,euid和fsuid都改成用户B了,这样就能够将通关结果保存下来。

在Linux里面,一个进程可以随时通过setuid设置用户ID,所以,游戏程序的用户B的ID还会保存在一个地方,这就是suid和sgid,也就是saveduid和savegid。这样就可以很方便地使用setuid,通过设置uid或者suid来改变权限。

除了以用户和用户组控制权限,Linux还有另一个机制就是capabilities。

原来控制进程的权限,要么是高权限的root用户,要么是一般权限的普通用户,这时候的问题是,root用户权限太大,而普通用户权限太小。有时候一个普通用户想做一点高权限的事情,必须给他整个root的权限。这个太不安全了。

于是,我们引入新的机制capabilities,用位图表示权限,在capability.h可以找到定义的权限。我这里列举几个。

#defineCAP_CHOWN0#defineCAP_KILL5#defineCAP_NET_BIND_SERVICE10#defineCAP_NET_RAW13#defineCAP_SYS_MODULE16#defineCAP_SYS_RAWIO17#defineCAP_SYS_BOOT22#defineCAP_SYS_TIME25#defineCAP_AUDIT_READ37#defineCAP_LAST_CAPCAP_AUDIT_READ对于普通用户运行的进程,当有这个权限的时候,就能做这些操作;没有的时候,就不能做,这样粒度要小很多。

cap_permitted表示进程能够使用的权限。但是真正起作用的是cap_effective。cap_permitted中可以包含cap_effective中没有的权限。一个进程可以在必要的时候,放弃自己的某些权限,这样更加安全。假设自己因为代码漏洞被攻破了,但是如果啥也干不了,就没办法进一步突破。

cap_inheritable表示当可执行文件的扩展属性设置了inheritable位时,调用exec执行该程序会继承调用者的inheritable集合,并将其加入到permitted集合。但在非root用户下执行exec时,通常不会保留inheritable集合,但是往往又是非root用户,才想保留权限,所以非常鸡肋。

cap_bset,也就是capabilityboundingset,是系统中所有进程允许保留的权限。如果这个集合中不存在某个权限,那么系统中的所有进程都没有这个权限。即使以超级用户权限执行的进程,也是一样的。

这样有很多好处。例如,系统启动以后,将加载内核模块的权限去掉,那所有进程都不能加载内核模块。这样,即便这台机器被攻破,也做不了太多有害的事情。

cap_ambient是比较新加入内核的,就是为了解决cap_inheritable鸡肋的状况,也就是,非root用户进程使用exec执行一个程序的时候,如何保留权限的问题。当执行exec的时候,cap_ambient会被添加到cap_permitted中,同时设置到cap_effective中。

每个进程都有自己独立的虚拟内存空间,这需要有一个数据结构来表示,就是mm_struct。

每个进程都有自己独立的虚拟内存空间,这需要有一个数据结构来表示,就是mm_struct。文件与文件系统每个进程有一个文件系统的数据结构,还有一个打开文件的数据结构。

THE END
1.操作系统分类操作系统有哪些操作系统分类 桌面操作系统:Windows、macOS、Linux 服务器操作系统:Linux(安全稳定免费)、Windows Server(付费、占有率低) 嵌入式操作系统:Linux(文件小) 应用:机顶盒、网络电视、程控交换机、手机PDA 移动设备操作系统:ios、Andriodhttps://blog.csdn.net/qq_44278313/article/details/124115906
2.常见的操作系统有哪些类型?常见的五大操作系统类型详细介绍微软还开发了适合服务器的操作系统,像windows server 2000,windows server 2003.一般的台式机不会去装此类的操作系统,因为最初的设计是为服务器安装的,个硬件的要求都不一样的。 2、UNIX操作系统 UNIX基本都是安装在服务器上,没有用户界面,基本上都是命令操作。所以你进入该系统的时候就是一个黑乎乎的界面,然后就https://www.jb51.net/os/other/815947.html
3.操作系统有哪些?全面解析主流操作系统及其应用嘲操作系统是现代计算机和移动设备的核心,选择合适的操作系统可以显著提升用户的工作和生活效率。Windows、macOS、Linux、Android和iOS各有其独特的优势和应用场景,用户应根据自身需求进行选择。希望本文能够帮助读者更全面地了解主流操作系统及其应用场景,从而做出更明智的选择。 下载地址 (学会使用小白系统工具,让你的生活多https://www.163987.com/jiaocheng/151927.html
4.操作系统有哪些Windows XP是许多用户刚开始接触电脑时所用的操作系统,从它开始慢慢的掌握了电脑的操作,好不容易已经操作熟练了,又来一个新的操作系统,许多用户就有抵触的情绪,干嘛要换,旧版的用的挺好的,新的`我又不会用,用的很不习惯也,换了新的有得重新学习,凭啥呀,不爽。 https://m.oh100.com/kaoshi/caozuoxitong/628831.html
5.操作系统有哪些操作系统都有哪些电脑知识常见的操作系统有”windows“、”macos“、”linux“、”android“、”ios“和”unix“六种:1、windows操作系统,由微软公司开发的操作系统;2、macos操作系统,由苹果公司开发的操作系统,用于苹果的mac电脑和笔记本电脑;3、linux操作系统,开源的、免费的操作系统,具有高度的定制性和安全性;4、android操作系统,由谷歌公司https://m.php.cn/faq/642247.html
6.思科网络技术学院教程(第6版):网络简介第2章“配置网络操作系统”:简要地介绍大多数思科设备使用的操作系统—Cisco IOS。描述IOS的基本用途和功能以及访问IOS的方法。这一章还将演示IOS命令行界面的用法以及IOS设备的基本配置。 第3章“网络协议和通信”:探讨规则或协议对网络通信的重要性。将探索OSI参考模型和TCP/IP通信簇,以及这些模型如何提供必要的协议https://www.epubit.com/bookDetails?id=N15003
7.一文速览计算机的前世今生表1 计算机早期发展重点列举 Part 04 照向未来 了解计算机发展史有何意义呢? 一方面,千变万变,底层不变。 算力网络、智慧家庭、元宇宙,是近年来较热门的事物,它们较大程度上是基于现有技术进行整合创新,如云计算、物联网等等,而这二者归根结底是CPU、内存、操作系统、网络等等在强力支撑。了解根源,透过现象看本质https://www.51cto.com/article/746805.html
8.把这些计算机基础知识学完后,我飘了!Windows 操作系统的特征 Windows 操作系统是世界上用户数量最庞大的群体,作为 Windows 操作系统的资深用户,你都知道 Windows 操作系统有哪些特征吗?下面列举了一些 Windows 操作系统的特性 Windows 操作系统有两个版本:32位和64位 通过API函数集成来提供系统调用 https://maimai.cn/article/detail?fid=1400649709&efid=VmhIiOBVBH0ua86U1tJoEA
9.西藏农牧学院网络安全宣传周专栏如丢失密码、疏忽大意、非法操作等都可以对网络造成极大的破坏。 3 系统的漏洞和陷门 操作系统和网络软件不可能是完美无缺的 而这些漏洞或者陷门正好就给黑 客提供了一个入口 成为他们的首选攻击目标。而大部分的黑客攻入网络内部 都是因为安全措施不完善所致的。 http://www.xza.edu.cn/wlaqxcz/News_View.asp?NewsID=484
10.收银工作计划(精选20篇)1、监督夜班对商场的门,关闭和夜间红外报警系统操作工作。 2、对夜班值班情况每周进行二次以上的突击检查。 收银工作计划 2 新的一年到来了,为了在新的一年能够把工作做到最好,特制定工作计划如下: 一、全面提高个人业务技能 除本部门业务技能外,多学习营运方面知识,了解公司的经营状况及未来发展方向,确立目标,不局https://www.unjs.com/fanwenwang/gzjh/20220119165705_4691471.html
11.中标麒麟高级服务器操作系统系统管理员手册中标麒麟高级服务器操作系统-系统管理员手册.pdf,中标软件 中标麒麟高级服务器操作系统(龙芯) 系统管理员手册 目录 1 基本系统设置 8 1 . 1 系统地区和键盘配置8 1.1.1 配置系统地区 8 1.1.2 其他资源10 1 . 2 日期和时间配置 10 1.2.1 T IMEDAT ECT L 工具使用说明 10 1.https://m.book118.com/html/2022/0705/8026042070004115.shtm
12.操作系统(豆瓣)本书既是关于操作系统概念、结构和机制的教材,目的是尽可能清楚和全面地展示现代操作系统的本质和特点;也是讲解操作系统的经典教材,不仅系统地讲述了操作系统的基本概念、原理和方法,而且以当代流行的操作系统Windows 8、UNIX、Android、Linux为例,展现了当代操作系统的本质和特点。全书共分背景知识、进程、内存、调度、https://book.douban.com/subject/26993995/
13.列举你所知道的计算机操作系统,手机有哪些操作系统系统换了 https://ask.zol.com.cn/x/6550534.html
14.恶意代码分析实战第七章分析恶意Windows程序Autoruns工具列举在操作系统启动时会自动启动运行的代码 常用注册表函数 恶意代码常使用一些注册表函数来操作注册表,以便可以实现开机自启动,下面是常见的注册表。 RegOpenKeyEx打开一个注册表进行编辑和查询。有些函数允许你查询和编辑-一个注册表键,而不用先打开它,但是大多数程序还是会先使用RegOpenKeyEx。 https://www.jianshu.com/p/845cbfb96077
15.开源嵌入式操作系统和开源手机操作系统ERP优易软件它最初是自由软件基金会为其GNU操作系统所写,但目前最主要的应用是配合Linux内核,成为GNU/Linux操作系统一个重要的组成部分。 在通用的PC和Server中,Linux(ubuntu, Redhat, CentOS etc.)默认提供对glibc的支持;但是在嵌入式应用中,考虑到系统对os大小的要求和简化系统的复杂度等因素,并不一定支持glibc,而是支持http://www.uesoft.com/forum.php?mod=viewthread&tid=15821
16.电力安全的论文网络信息技术的信息共享的属性和基本前提决定了一定的安全漏洞会存在于电力系统的各个计算机网络信息系统中。网络协议漏洞也是危害电力系统网络信息安全的重要因素之一。 2.3工作环境 时下众多电力企业的计算机操作系统以及相关数据库系统等工作环境存在诸如网路通信协议错误以及存在于自身体系中的安全漏洞等等诸多不确定因素带来https://www.ruiwen.com/lunwen/6833724.html