一.理解系统调用和库函数概念
• 1.在开发角度,操作系统对外会表现为⼀个整体,但是会暴露自己的部分接口,供上层开发使用, 这部分由操作系统提供的接口,叫做系统调用。
• 2.系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开 发。
对1的理解:
就好比银行,人们去取钱总不能直接自己去数要取好多钱吧,而是通过银行的柜台或者窗口取
(即访问操作系统不能直接访问而是通过系统调用接口)

所以有系统调用才能访问到操作系统,Linux,Windows等等的操作系统都是用c语言写的,系统调用的就是c语言的相关函数(输入参数,返回值)
输入:用户->os 输出:os->用户
所以系统调用的本质也可以说就是用户和OS之间进行数据交互
对2的理解:

例如当老人去银行取钱但不知道方法时,大堂经理就会帮助老人(即系统调用对用户要求高,用户不知道方法,但有了库相当于大堂经理,就有利于我们理解和二次开发
二.进程
• 课本概念:程序的⼀个执⾏实例,正在执⾏的程序等
• 内核观点:担当分配系统资源(CPU时间,内存)的实体。
当磁盘的可执行程序加载到内存时,操作系统需要对这些程序进行管理,可是操作系统并不知道这些可执行程序具体是哪一个
PCB 描述进程
• 进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
• 课本上称之为PCB(process control block), Linux 操作系统下的 PCB 是: task_struct
os内部会对每一个加载到内存的可执行程序创建一个struct结点,这个结点包含对应程序的所有属性,例如代码地址,数据地址,id等等,这样每一个进程都有一个节点。进程的所有属性都可以直接或者间接通过 task_struct管理。
现在对进程的理解:
进程=PCB(task_struct)+代码和数据
进程=内核数据结构对象+代码和数据
所以加载到内存的进程,除了把代码和数据加载到内存,还会在os内部为其创建一个task_struct节点存放该进程的属性,这些节点相互关联就会形成链表,所以在os对进程的管理就转换为对链表的增删查改
总结os对进程的管理符合先描述再管理的特点:即先有描述进程的tast_struct,再有相应的数据结构类型组织(eg:链表),对进程的管理即为对数据结构的增删查改
task_struct 内容分类
• 标⽰符:描述本进程的唯⼀标⽰符,⽤来区别其他进程。
• 状态:任务状态,退出代码,退出信号等。
• 优先级:相对于其他进程的优先级。
• 程序计数器:程序中即将被执⾏的下⼀条指令的地址。
• 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
• 上下⽂数据:进程执⾏时处理器的寄存器中的数据[休学例⼦,要加图CPU,寄存器。
• I/O状态信息:包括显⽰的I/O请求,分配给进程的I/O设备和被进程使⽤的⽂件列表。
• 记账信息:可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。
• 其他信息
• 具体详细信息后续会介绍
getpid
(在头文件 <unistd.h> 和<sys/types.h>中)
getpid 是一个系统调用函数,用于获取当前进程的进程 ID(PID)。在 Unix/Linux 系统中,每个进程都有一个唯一的进程标识符(PID),通常是一个正整数。getpid 函数返回调用进程的 PID。
- 返回值类型为
pid_t,通常是一个整数类型(如int)。 - 函数无需参数。

运行结果:

查看进程的方式:
(1)可以在另外一台Linux机器上用ps和top查看myprocess的进程
linux下可以用;或者 &&连接两条语句

ps axj 和ps ajx两者功能相似,但 ps axj 更常用于分析进程层次结构(查看进程的父子关系),而 ps ajx 提供类似的详细信息。)
我们历史上执行的所有指令,工具,自己的程序运行起来都是进程,在这里grep也是一个进程所以命令行中也会有grep的进程(可以通过grep-v grep反向匹配不显示出来)
怎么杀掉一个进程?
这里可以在运行./myprocess的机器上直接ctrl+c杀掉,也可以在另外一台机器上
kill -9 10415(pid)杀掉进程
(2)通过 /proc系统文件夹查看
/proc 是一个虚拟文件系统,提供内核和进程信息的动态接口**。它不占用磁盘空间**,而是实时反映系统状态。
如:要获取PID为1的进程信息,你需要查看 /proc/1 这个⽂件夹。


eg:

- 复习一下
d:仅显示目录本身的信息,而非其包含的文件或子目录。-l:以长格式(详细信息)显示结果,包括权限、所有者、大小等。-a:显示所有文件,包括隐藏文件
proc记录当前所有进程的信息而proc目录下所有的文件每一个数字目录都代表一个进程的pid
ls /proc/pid -l查看当前进程的所有属性

exe为进程对应的可执行文件的绝对路径,如果删除那程序还会不会继续运行呢?

答案是程序还会继续运行,因为程序已经加载到了内存当中而删除的是磁盘的文件,不会影响。但如果再kill杀掉程序后再./myprocess就不行了因为磁盘已经没有exe执行文件不能加载到内存当中。此时就需要重新make编译了

并且当删除了exe后再ls
cwd(current work dir)---当前工作目录
当前工作目录(Current Working Directory, CWD)是指用户或进程当前所处的目录路径 。所有相对路径的操作(如文件访问、命令执行)均基于此目录展开。
所以进程会记录下自己的当前路径,当再创建文件时就会把进程自己的当前路径和新创建的文件的路径拼接构成一个新的路径,这样就在当前路径新建文件了
chdir更改当前工作目录

当工作目录变为"/home/ysm"时创建的"hello.txt"就在"/home/ysm"目录下

所以也可以推断cd的底层就是chdir
getppid()获取父进程的pid

每次运行时可以看到当前进程的pid发生变化,而父进程的pid并没有发生变化

复习:CTRL+r 再加关键字找回以前的指令

bash--命令行解释器
2点理解
(1)命令行解释器本质就是一个进程
(2)我们在命令行启动自己的程序时,我们的程序都是bash的子进程
每一次登录云服务器,os就会给每一个用户分配一个bash
eg:现在登陆了2台linux机器则就有2个bash


这个东西就是bash打印出来的一个字符串,现在就加载到这(就好比printf后再scanf),所以我们输入的命令都是以字符串形式交给了bash。
即例如ls,pwd.......这些指令都是进程,并且他们的父进程都是bash
bash是怎么做到创建子进程的呢?
用代码创建子进程的方式
fork:


fork有两个返回值,(1)成功的话,子进程的pid返回给父进程,0返回给子进程(2)失败的话,-1返回给父进程,没有子进程创建
myprocess.c:

运行结果可以看到有两个进程分别是父进程26771和子进程26772

怎么理解?

子进程的pcb由父进程的pcb拷贝,大部分属性都是相同的(pid不相同)则子进程默认会指向父进程的代码和数据 所以子进程在被调度时执行父进程之后的代码(即子进程和父进程都会去执行printf("进程开始运行pid:%d\n",getpid())。在这里子进程没有自己的代码和数据因为还没有程序加载到子进程。
父子执行不同的代码
根据fork()的返回值来分情况讨论


fork以后的代码是父子共享的只不过子进程进入id==0这种情况的执行流,父进程进入else这种情况的执行流,从而做到父子执行不同的代码
3个问题
1.为什么fork给父子进程返回各自不同的返回值?
父:子=1:n
即父进程可以有多个子进程而子进程只能有一个父进程,所以父进程要管理多个子进程就得通过管理每个子进程单独的pid来实现,即得返回子进程的pid给父进程,而子进程则通过getppid()就可以获取父进程pid不用再单独返回
2.为什么一个函数会返回两次?

如果一个函数已经return了那么该函数的核心功能已经做完了
在上面图中进入fork函数内部时,在return前就已经做完了fork函数的核心功能,子进程已经被创建甚至被调度了所以到执行return语句时就会有父进程和子进程的两次return
3.为什么一个变量==0又>0导致if和else同时实现?
在现在先理解一半
首先得知道一个结论即:进程具有独立性
(1)父进程的pcb和子进程的pcb内核数据结构互不影响
(2)代码只读只执行,为共享的,但父子不能修改所以也不会相互影响
(3)父子在数据层面默认是共享的,但如果有人把父子的任何一方进行数据修改,那os就会把数据在底层先拷贝一份形成两份,让目标进程只修改这个拷贝,不影响原来的那一份,实现数据的独立
eg:子进程要修改数据,os就会拷贝一份,让子进程去访问这个拷贝的新的一份,而父进程访问的还是原来的一份,数据不会相互影响。(写时拷贝)


同时进入了子进程和父进程的情况,只不过父进程休眠了1s所以先打印子进程里面的代码,1s结束后再去执行父进程里面的代码
可以看到子进程的flag由0->1父进程的flag还是0,并没有受到影响