操作系统导论-基础篇一

一、操作系统-前期知识的积累

1.概念

CPU简单概念是一个处理器,用来处理和运算。

一个CPU在一刻时间只能处理一件事,但为甚麽在现实生活中,我们的电脑好像可以同时进行多个程序,跟我刚才说的好像矛盾。

2.虚拟化

举例:假如你的电脑CPU只有一个(挺惨的,就只有一个CPU),这样在实际上现实上,你都只有一个CPU,但是通过操作系统,就是能虚构出很多CPU(用来迷惑你)。可是还是原来同一个。这是怎么做的咧,就是暂停和切换。

假如你要运行两个程序,现在你的CPU只有一个,那样的话,如何迷惑你,让你感觉你的两个程序都在同时运行。答案是现进行一个程序,运行一段时间,暂停这个程序后运行另一个程序,过一段时间,另一个程序也暂停,运行原来那个程序,因为这暂停和切换的时间很短,在用户(人类)看来根本意识不到,所以你就感觉,这两个程序在同时运行。这就是它能迷惑你的本质。

3.操作系统

什么是操作系统,简单的理解就是能够操作与安排系统并进行系统的一个程序。“操作系统”本身就是一个程序,不过它与一般的程序不同,就是它是能够调用CPU,将硬件资源进行虚拟化的一个工具。举个例子:现在有个监狱(电脑),你是张三(内存与各项指令,及各种物理资源),你被关在里面(存放),然后有狱警(操作系统)在管理这个监狱。

4.进程与线程

线程是在进程里面,线程是在进程里面,线程是在进程里面。(重要的事情说三遍)

4.1.进程,线程是什么?

进程,简单的讲就是你现在所运行的程序。线程,就是你的程序下的子程序。如你用微信(进程)点开聊天界面跟人聊天(线程)。在这里,必须特殊说明,就是进程里面对的数据,有分自己的数据和别处的数据。

4.1.1自己的数据

在进程开始时就会存有,所以可以直接调用。

4.1.2别处的数据

该进程需要通过操作系统才能调用。如:你在美团(第一个进程)注册时,需要你的微信(另一个进程)信息(数据),这时候需要你(操作系统)来进行同意和给与。

4.2 线程的使用

在进程中通过操作系统,进程可以直接使用CPU和调用一些虚拟和物理资源的资源,但在线程里,线程只能调用进程里的数据和使用,不能直接去往其他地方。好比你在监狱里你是张三,你在坐牢(进程),在没有狱警(操作系统)的安排下,你只能在牢里做事,睡觉洗澡(线程)。

但线程之间只能一个进行。如同你在微信聊天界面上在同一时间只能与一个人进行聊天。(当然罗老师和时间管理大师除外),如果一个线程已经在进行了,那么其他线程就会停止。但注意这里是停止了,但在用户使用使用中,用户是感觉不出来的(除非手机卡了,这也是一种,长时间只进行一个线程或进程的现象),就像上面所讲的有多个虚拟CPU给用户(人类)带来的迷惑性。

5.并发

就是在进程与进程之间,线程与线程之间,同时发生需要同时使用内存,算力和CPU处理的现象产生。(这个留到下一篇,这个太长了,懒得写了,不过点个赞,这就来做)

二、操作系统-前期代码知识

1.fork()系统调用

/*fork()系统调用
不同
系统调用fork0用于创建新进程[C63]。但要小心,这可能是你使用过的最奇怪的接口。别非
体来说,你可以运行一个程序,仔细看这段代码,建议亲自键入并运行!
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main (int argc, char *argv[])
{
printf ("hello world (pid:d)\n", (int) getpid());
int rc = fork();

if (rc<0){
// fork failed; exit
fprintf (stderr, "fork failed\n");
exit(1);
}

else if (rc == 0){// child (new process)
printf("hello, I am child (pid:d)\n", (int) getpid());
} 

else{
// parent goes down this path (main)
printf ("hello, I am parent of d (pid:d)\n",
rc, (int) getpid());
}
return 0;

}

当它刚开始运行时,进程输出一条helloworld信息,以及自己的进程的系统中,如果要操作某个进程(如终止进程),就要通过PID来指明。到目前为止,一切正常。紧接着有趣的事情发生了。进程调用了fork0)系统调用,这是操作系统提供的创建新进共巩码程的方法。新创建的进程几乎与调用进程完全一样,对操作系统来说,这时看起来有两个完仍全一样的p1程序在运行,并都从fork()系统调用中返回。新创建的进程称为子进程(child),原来的进程称为父进程(parent)。子进程不会从main()函数开始执行(因此hello world信息只输出了一次),而是直接从fork()系统调用返回,就好像是它自己调用了fork()。重点你可能已经注意到,子进程并不是完全拷贝了父进程。具体来说,虽然它拥有自己的地址空间(即拥有自己的私有内存)、寄存器、程序计数器等,但是它从fork()返回的值是不同的。父进程获得的返回值是新创建子进程的PID,而子进程获得的返回值是0。这个差
别非常重要,因为这样就很容易编写代码处理两种不同的情况(像上面那样)。你可能还会注意到,它的输出不是确定的。子进程被创建后,我们就需要关心系统中的两个活动进程了:子进程和父进程。假设我们在单个CPU的系统上运行(简单起见),那么子进程或父进程在此时都有可能运行。在上面的例子中,父进程先运行并输出信息。在其他情况下,子进程可能先运行,会有下面的输出结果:
prompt>./p1
hello world (pid:29146)
hello, I am child (pid:29147
hello, I am parent of 29147 (pid:29146)
prompt>
CPU调度程序决定了某个时刻哪个进程被执行,我们稍后将详细介绍这部分内容。由于CPU调度程序非常复杂,所以我们不能假设哪个进程会先运行。事实表明,这种不确定性会导致一些很有趣的问题,特别是在多线程程序中。学习并发时,我们会看到许多不确定性。

2.wait()系统调用

#include <stdl1b,h>
#include <atdlo,h>
#includo <unlstd.h>
#include <gyn/wait,h>
//父进程,先被阻塞wait,后调用子进程(线程)

int main(int arge, char *argv[])
{
printf("hello world (pid:sd)\n", (int)getpid());

int rcm fork();

if (rc<0){
// fork failed; exit

fprintf(stderr, "fork falled\n");

exit(l);

} else 1f (rcum o) { // child (newprocess)

printf ("hello, I am child lpid:td)\n", (int) getpid());

}else{
// parent goes down this path (main)

int wc m wait (NULL);

printf ("hello, I am parent of ad (wc:d) (pid:d)\n",rc, wc, (int) getpid());
}
return 0;
}

在父进程调用wait(),延迟自己的执行,直到子进程执行完毕。当子程结束时,wait(才返回父进程)上面的代码增加了wait()调用,因此输出结果也变得确定了。这是为什么呢?想想(等你想想看...好了)
下面是输出结果:
prompt>./p2
hello world (pid:29266)
hello, I am child (pid:29267)
hello, I am parent of 29267 (wc:29267) (pid:29266)
prompt>
通过这段代码,现在我们知道子进程总是先输出结果。为什么知道?好吧,它可能只是碰巧先运行,像以前一样,因此先于父进程输出结果。但是,如果父进程碰巧先运行,它会马上调用wait()。该系统调用会在子进程运行结束后才返回。因此,即使父进程先行,它也会礼貌地等待子进程运行完毕,然后wait()返回,接着父进程才输出自己的信息

3.exec()系统调用

prompt>./p3
hello world (pid:29383)
hello, I am child (pid:29384)
29
107
1030p3.c
hello, I am parent of 29384 (wc:29384) (pid:29383)
prompt>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main (int argc, char *argv[])
{

printf("hello world (pid:d)\n", (int) getpid());

int rc = fork();

if (rc<0){
// fork failed; exit

fprintf (stderr, "fork failed\n");

exit(1);

}else if (rc == 0) { // child (new process)

printf("hello, I am child (pid:d)\n",(int)getpid());

char *myargs[3];
myargs [O] = strdup("wc");//program:"wc"(word count)
myargs [1] = strdup("p3.c");//argument: file to count
myargs [2] = NULL;
// marks end of array
execvp (myargs[O], myargs);//runs word count

printf("this shouldn't print out");

}else{ // parent goes down this path (main)

int wc = wait(NULL);
printf ("hello, I am parent of d (wc:d) (pid:d)\n"rc, wc, (int) getpid());

}

return O;
}

调用fork()、wait()和exec()(p3.c)在这个例子中,子进程调用execvp()来运行字符计数程序wc。实际上,它针对源代码文件运行wc,从而告诉我们该文件有多少行、多少单词,以及多少字节。
fork()系统调用很奇怪,它的伙伴exec()也不一般。给定可执行程序的名称(如wc)及需要的参数(如原件)后,exec(会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据),堆、栈及其他内存空间也会被重新初始化。然后操作系统就执行该程序,将参数通过argv传递给该进程。因此,它并没有创建新进程,而是直接将当前运行的程序(以前的)替换为不同的运行程序(wc)。子进程执行exec()之后,几乎就像源代码从未运行过一样。对exec()的成功调用永远不会返回。

文章转载至操作系统导论-基础篇一 – 布尔博客

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值