我希望找到这里的学弟学妹能基于我的内容完成的更好,这里的代码和思路应该是你们的下限!!
我分享这些笔记的初衷是因为我觉得csdn上与之相关的不少博客都收费,但是我当时做的时候,我觉得就那么一点东西有什么可收费的,咱们当程序员的最重要的是什么?是开源精神!所以我把自己的笔记都公开分享了,我觉得都是软院一家人,有些不必要的坑我可以帮你们踩,你们可以把时间花在加深对操作系统的理解过程上,而不是像我当时一样整天周转于各种博客看各种配置bug,一天下来看似解决了很多问题,实际这个实验真正需要我理解的地方我实际理解根本不够。这是我踩的最大的坑!所以我把自己很菜的笔记分享在这里,如果能对你们有启迪或者帮到你们我都会很开心!这是我的初衷!
如果你们有什么看不懂的问题,经过自己思考仍然无法解决我非常欢迎你们给我私信,但是我不希望现在打开私信几乎都是上来问候也没有就直接管我要代码要报告的!
如果这些笔记让你们觉得可以直接私聊我当伸手党我会很难受!并且我希望这种人别来找我,因为我觉得所有该写的都写了,我没有任何义务要帮那些想要敷衍老师的人完成作业!
吉林大学操作系统上机(实验二:处理机调度——实时调度算法EDF和RMS)
菜鸡博客,只是作为学习记录。。。操作系统要好好学!
我们老师建议体会在linux下的gcc编程,所以如果不用学校提供的上机电脑,在自己电脑上体会的话我们要搞一个linux环境。
这里的话有很多途径(比如下VMware虚拟机安装对应的linux版本iso文件,装虚拟机有硬件隔离,做一些安全性测试比较实用),我使用的是WSL(微软拿出的一款让鱼和熊掌兼得的方案 :Windows Subsystem for Linux,也就是 Windows 系统中自带 Linux 子系统)
对应的配置环境可以参考博客:
Windows 10开启Linux子系统
tips:
\\wsl$
ls
cd 目录名
cd ..
mkdir 目录名
mount –t vfat /dev/sdb /mnt/usb
(优盘就挂载在/mnt/usb下.)su
su 用户名
ps aux
表格名称 | 各自含义 |
---|---|
USER | 用户 |
PID | 进程号 |
%CPU | 进程占用的CPU百分比 |
%MEM | 占用内存的百分比 |
STAT | 进程状态 例如S 处于休眠状态;R 正在运行;Z 僵尸进程不存在但暂时无法消除 |
COMMAND | 命令 |
gcc是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%。
而我们知道,一个C/C++程序从开始编码到生成二进制可执行文件至少要经过4个步骤:
所以gcc中的-c
会生成对应目标文件,-o
命令生成二进制可执行文件。基本流程就是:
vi
[目标文件].cgcc
[目标文件].c -o
[可执行文件]`./
[可执行文件]gcc最基本的用法是∶
gcc [options] [filenames]
-o output_filename
-lname
-Ldir
举个例子: gcc –lm -L/home/mylib ts.c –o ts.out
这时,预编译、编译连接ts.c,生成一个指定名称为ts.out的可执行文件,在编译过程中需要引入m库.对于一些标准库来说,我们没有必要指出路径.只要它们在起缺省库的路径下就可以了 , 系统的缺省库的路径/usr/lib /usr/local/lib /lib在这三个路径下面的库,我们可以不指定路径 。
①创建一个.c文件vi hello.c
②按英文输入法下的i
进入写状态,开始编写代码内容,下方会变为”INSERT“
③输完后按Esc
退出写状态,再输入:wq
保存退出
④指令:gcc hello.c -o helloworld
gcc编译程序,编译完成后,在你的文件中出现一个helloworld运行文件。(输入ls
可以查看)
3.指令:./helloworld
可以看到成功输出。前期工作结束,开始我们的操作系统实验课练习。
深刻理解线程与进程的概念,掌握线程与进程在组成成分上的差别,以及与其相适应的通讯方式和应用目标。
pid=fork()
创建一个子进程,子进程是父进程的完整复制,fork()创建的新进程是当前进程(返回值除外)的副本。子进程的所有资源都继承父进程,得到父进程资源的副本,既然为副本,也就是说,二者并不共享地址空间(采用写时复制技术)。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID(pid);
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
参考来源
#define _GNU_SOURCE
#include
int clone(int (*func)(void*),void *child_stack,int flags,void *func_arg,....
/*pid_t *ptid,struct user_desc *tls,pid_t *ctid*/);
Return process ID of child on success,or -1 on error
如同fork(),由clone()创建的新进程几近于父进程的翻版。但是与fork()不同的是,克隆生成的子进程继续运行时不以调用处为起点,转而去调用以参数func所指定的函数,func又称为子函数。调用子函数时的参数由func_arg
指定。经过转换,子函数可对改参数的含义自由解读,例如可以作为整型值(int),也可以视为指向结构的指针。
当函数func返回或者是调用exit()(或者_exit())之后,克隆产生的子进程就会终止。照例,父进程可以通过wait()一类函数来等待克隆子进程。
因为克隆产生的子进程可能共享父进程内存,所以它不能使用父进程的栈。调用者必须分配一块大小适中的内存空间供子进程的栈使用,同时将这块内存的指针置于参数child_stack
中。
参数flags服务于双重目的。首先,其低字节中存放着子进程的终止信号,子进程退出时其父进程将收到这一信号。(如果克隆产生的子进程因信号而终止,父进程依然会收到SIGCHLD信号)该字节也可能为0,这时将不会产生任何信号。
clone()函数中的flags参数是各位掩码的组合:
掩码 | 意义 |
---|---|
共享文件描述符:CLONE_FILES | 如果指定了该标志,父子进程会共享同一个打开文件描述符表。也就是说,无论哪个进程对文件描述符的分配与释放都会影响宁一个进程。 |
共享与文件系统相关的信息:CLONE_FS | 如果指定了该标志,那么父子进程将共享与文件系统相关的信息:权限掩码、根目录以及当前工作目录。也就是说无论在哪个进程中调用umask()、chdir()或者chroot(),都将影响到另一个进程。 |
共享对信号的处置设置:CLONE_SIGHAND | 如果设置了该标志,那么父子进程将共享同一信号处置表。无论在哪个进程中调用sigaction()或者signal()来改变对信号处置的设置,都会影响其他进程对信号的处置。 |
共享父进程的虚拟内存:CLONE_VM | 如果设置了该标志,父子进程将会共享同一份虚拟内存页。无论哪一个进程更新了内存,或是调用了mmap()、munmap(),另一进程同样会观察到变化。 |
线程组:CLONE_THREAD | 若设置了该标志,则会将子进程置于父进程的线程组中。如果未设置该标志,那么会将子进程置于新的线程组中。 |
线程库支持:CLONE_PARENT_SETTID、CLONE_CHILD_SETTID和CLONE_CHILD_CLEARTID | 为实现POSIX线程,Linux2.6提供了对CLONE_PARENT_SETTID、CLONE_CHILD_SETTID和CLONE_CHILD_CLEARTID的支持。这些标志将会影响clone()对参数ptid、ctid的处理。如果设置了CLONE_PARENT_SETTID,内核会将子进程的线程ID写入ptid所指向的位置。如果设置了CLONE_CHILD_SETTID,那么clone()会将子线程的线程ID写入指针ctid所指向的位置。如果设置了CLONE_CHILD_CLEARTID,则会在子进程终止时将ctid所指向的内存清零。 |
线程本地存储:CLONE_SETTLS | 如果设置了该标志,那么参数tls所指向的user_desc结构会对线程所使用的线程本地存储缓冲区加以描述。 |
共享systemV信号量的撤销值:CLONE_SYSVSEM | 如果设置了该标志,父子进程会将共享同一个SystemV信号量撤销值列表。 |
每进程挂载命名空间:CLONE_NEWNS | 将子进程的父进程置为调用者的父进程:CLONE_PARENT |
进程跟踪:CLONE_PTRACE和CLONE_UNTRACED | 如果设置了CLONE_PTRACE且正在跟踪子进程,那么也会对子进程进行跟踪。从Linux2.6起,即可设置CLONE_UNTRACED标志,这也意味着跟踪进程不能强制其子进程设置为CLONE_PTRACE |
挂起父进程直至子进程退出或者调用exec():CLONE_VFORK | 如果设置了该标识,父进程将一直挂起,直至子进程调用exec()或者_exit()来释放虚拟内存资源为止。 |
参考来源
// fd[0]是管道读取端的fd(文件描述符)
// fd[1]是管道写入端的fd
// 成功时返回:0
// 错误时为-1
int pipe(int fds[2]);
从概念上讲,管道(pipe)是两个进程之间的连接,它使得一个进程的标准输出成为另一个进程的标准输入(通信)。
管道仅是单向通信,一个进程向管道写入,另一个进程则从管道中读取。这是一个被视为“虚拟文件”的主内存区域。创建的进程及其所有子进程都可以使用管道进行读写。一个进程可以写入这个管道,而另一个相关的进程可以从中读取。如果管道为空,而且管道的输入口至少有一个进程打开着,那么一个进程试图读取管道的时将会被挂起。
wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束:
如果执行成功则返回子进程识别码(PID) ,如果有错误发生则返回返回值-1:
pid_t pid;
int status=0;
i=wait(&status);
i返回的是子进程的识别码PID;status中存的是子进程的结束状态;可用宏WIFEXITED(status)得到子进程的exit(3)的状态也是3;
#include
#include
#include
#include
#include
int main(int argc,char *agrv[]){
pid_t pid;//pid表示fork函数返回地值
pid=fork();//创建一个子进程
if(pid<0)//异常
perror("error in fork!\n");
if(pid==0){//父进程识别到这是子进程在运行
int i=0;
for(i=0;i<4;i++){
printf("this is son process,the process's pid is %d.\n",getpid());
sleep(2);//让子进程睡眠2秒,看看父进程的行为
}
exit(1);
}
else{//子进程结束,父进程运行
int status=0;
wait(&status);
if(WIFEXITED(status)!=0){
printf("son process is over and return %d \n",WIFEXITED(status));
}
printf("this is father process\n");
}
return 0;
}
sem_post函数
int sem_post(sem_t *sem);
作用是给信号量的值加上一个“1”。 当有线程阻塞在这个信号量上时,调用这个函数会使其中一个线程不再阻塞,选择机制是有线程的调度策略决定的。
sem_wait函数
int sem_wait(sem_t * sem);
它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
①一个线程等待"条件变量的条件成立"而挂起;
②另一个线程使"条件成立"(给出条件成立信号)。
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
pthread_mutex_init(&mutex,NULL);//初始化
pthread_mutex_lock(&mutex); //互斥锁上锁
pthread_mutex_unlock(&mutex);//互斥锁解锁
注意:1)函数都是原子操作,可以为理解为一条指令,不会被其他程序打断
2)pthread_cond_wait,先会解除当前线程的互斥锁,然后挂线线程,等待条件变量满足条件。一旦条件变量满足条件,则会给线程上锁,继续执行pthread_cond_wait。
编译:gcc thread_test.c -o thread_test -lpthread
#include "sys/types.h"
#include "sys/file.h"
#include "unistd.h"
char r_buf[4]; //读缓冲
char w_buf[4]; //写缓冲
int pipe_fd[2];
pid_t pid1, pid2, pid3, pid4;
int producer(int id);
int consumer(int id);
int main(int argc,char **argv){
if(pipe(pipe_fd)<0){//传输出错
printf("pipe create error \n");
exit(-1);
}
else{
printf("pipe is created successfully!\n");
//谁占用管道谁进行数据传输
if((pid1=fork())==0)
producer(1);
if((pid2=fork())==0)
producer(2);
if((pid3=fork())==0)
consumer(1);
if((pid4=fork())==0)
consumer(2);
}
close(pipe_fd[0]); //需要加上这两句
close(pipe_fd[1]); //否者会有读者或者写者永远等待
int i,pid,status;
for(i=0;i<4;i++)
pid=wait(&status); //返回子进程状态
exit(0);
}
//生产者
int producer(int id){
printf("producer %d is running!\n",id);
close(pipe_fd[0]);
int i=0;
for(i=1;i<10;i++){
sleep(3);
if(id==1) //生产者1
strcpy(w_buf,"aaa\0");
else //生产者2
strcpy(w_buf,"bbb\0");
if(write(pipe_fd[1],w_buf,4)==-1)
printf("write to pipe error\n");
}
close(pipe_fd[1]);
printf("producer %d is over!\n",id);
exit(id);
}
//消费者
int consumer(int id){
close(pipe_fd[1]);
printf(" producer %d is running!\n",id);
if (id==1) //消费者1
strcpy(w_buf,"ccc\0");
else //消费者2
strcpy(w_buf,"ddd\0");
while(1){
sleep(1);
strcpy(r_buf,"eee\0");
if(read(pipe_fd[0],r_buf,4)==0)
break;
printf("consumer %d get %s, while the w_buf is %s\n",id,r_buf,w_buf);
}
close(pipe_fd[0]);
printf("consumer %d is over!\n", id);
exit(id);
}
输出结果:
由程序(1)结果可见,当一个进程改变其空间数据时,其他进程空间对应数据内容并未改变,说明在使用fork()语句创建的子进程与其父进程具有相对独立的地址空间,在解决生产消费的问题时,可以采用pipe()进行通信。因为子进程复制了父进程的打开文件表,所以pipe()所建立的通信管道可被子进程继承,生产和消费进程可以通过对同一管道文件的读写进行通讯。
程序(1)中,消费者从管道中接收生产者发送的数据,并且和自己存储区中的数据进行比较,两者的数据是不同的,说明两个进程拥有不同的存储空间。
其中我觉得最难理解的就是父子进程具有相对独立的地址空间这句话,那到底指向的地址一模一样呢?还是说子进程开辟了一个新的?而且我们知道了子进程几乎是父进程的完全复制(副本),子进程独有的仅仅是自己的进程号资源和计时器等,当我们改变子进程内容又不希望影响到父进程同时一定程度节约系统开销,我们实际上做的事情是:
#define _GNU_SOURCE
#include "sched.h"
#include
#include
#include
#include
#include "stdio.h"
#include "stdlib.h"
#include "semaphore.h"
#include "sys/wait.h"
#include "string.h"
int producer(void * args);
int consumer(void * args);
pthread_mutex_t mutex;
sem_t product;
sem_t warehouse;
char buffer[8][4];
int bp=0;
int main(int argc,char** argv){
pthread_mutex_init(&mutex,NULL);//初始化
sem_init(&product,0,0);
sem_init(&warehouse,0,8);
int clone_flag,arg,retval;
char *stack;
//clone_flag=CLONE_SIGHAND|CLONE_VFORK
//clone_flag=CLONE_VM|CLONE_FILES|CLONE_FS|CLONE_SIGHAND;
clone_flag=CLONE_VM|CLONE_SIGHAND|CLONE_FS| CLONE_FILES;
//printf("clone_flag=%d\n",clone_flag);
int i;
for(i=0;i<2;i++){ //创建四个线程
arg = i;
//printf("arg=%d\n",*(arg));
stack =(char*)malloc(4096);
retval=clone(producer,&(stack[4095]),clone_flag,(void*)&arg);
//printf("retval=%d\n",retval);
stack=(char*)malloc(4096);
retval=clone(consumer,&(stack[4095]),clone_flag,(void*)&arg);
//printf("retval=%d\n\n",retval);
usleep(1);
}
exit(1);
}
int producer(void *args){
int id = *((int*)args);
int i;
for(i=0;i<10;i++){
sleep(i+1); //表现线程速度差别
sem_wait(&warehouse);
pthread_mutex_lock(&mutex);
if(id==0)
strcpy(buffer[bp],"aaa/0");
else
strcpy(buffer[bp],"bbb/0");
bp++;
printf("producer %d produce %s in %d\n",id,buffer[bp-1],bp-1);
pthread_mutex_unlock(&mutex);
sem_post(&product);
}
printf("producer %d is over!\n",id);
exit(id);
}
int consumer(void *args){
int id = *((int*)args);
int i;
for(i=0;i<10;i++)
{
sleep(10-i); //表现线程速度差别
sem_wait(&product);
pthread_mutex_lock(&mutex);
bp--;
printf("consumer %d get %s in %d\n",id,buffer[bp],bp+1);
strcpy(buffer[bp],"zzz\0");
pthread_mutex_unlock(&mutex);
sem_post(&warehouse);
}
printf("consumer %d is over!\n",id);
exit(id);
}
编译:gcc -pthread clone.c -o clone.out
运行:./clone.out
输出结果(输出片段节选):
producer0 produce aaa in 0
producer1 produce bbb in 1
producer0 produce aaa in 2
producer1 produce bbb in 3
producer0 produce aaa in 4
producer1 produce bbb in 5
consumer0 get bbb in 5
consumer1 get aaa in 4
producer0 produce aaa in 4
producer1 produce bbb in 5
producer0 produce aaa in 6
producer1 produce bbb in 7
consumer0 get bbb in 7
consumer1 get aaa in 6
producer0 produce aaa in 6
producer1 produce bbb in 7
consumer0 get bbb in 7
consumer1 get aaa in 6
producer0 produce aaa in 6
producer1 produce bbb in 7
consumer0 get bbb in 7
consumer1 get aaa in 6
producer0 produce aaa in 6
producer1 produce bbb in 7
consumer0 get bbb in 7
consumer1 get aaa in 6
consumer0 get bbb in 5
consumer1 get aaa in 4
producer0 produce aaa in 4
producer1 produce bbb in 5
consumer0 get bbb in 5
consumer1 get aaa in 4
consumer0 get bbb in 3
consumer1 get aaa in 2
consumer0 get bbb in 1
consumer1 get aaa in 0
producer0 produce aaa in 0
producer0 is over!
producer1 produce bbb in 1
producer1 is over!
consumer0 get bbb in 1
consumer0 is over!
consumer1 get aaa in 0
consumer1 is over!
而由程序(2)结果可见,clone()语句在创建进程时,可通过参数设定子进程与父进程是否共享存储空间,从而可以创建真正意义上的线程。生产者和消费者进程共享内存,从而可以通过共享内存直接交换数据。但是多个进程共享内存需要互斥机制,程序中定义了临界区变量mutex和两个信号量product,warehouse,临界区变量用于共享内存操作的互斥,信号量分别实现了生产者和消费者的等待。
程序(2)中,消费者输出存储区中的数据,并且存储区中的数据随着生产者存入的数据而发生变化,说明clone()语句通过clone_flag的参数设定实现了共享内存。若在实验中除去CLONE_VM选项,将出现非预期的结果。
这里留个坑,上述clone系统的输出是理想化输出,我自己并没有跑出来,我得到的是没有输出的文件:
老师给我的解释是:因为clone线程这个实验比较老,linux更新很多代了,就可能存在linux对旧版本不兼容,更新后的头文件无法识别其中的一些参数,所以编译能通过但是参数识别出现问题对应功能就无法实现,这段代码问题出在clone_flag中的CLONE_SIGHAND,经过检验CLONE_SIGHAND加上之后clone()函数调用始终返回-1,也就是新建不成功,然后我试过把CLONE_SIGHAND去掉换成CLONE_VFORK,就能够clone()成功并返回一个进程号,并且输出能看到一些如下图所示(但是这样并不是我们追求的线程通信,因为这段代码只涉及线程,但是下面代码为什么在进程producer中明明应该有10个循环但显示只有8个然后一直卡着了,我还没想通,或者换句话说CLONE_VFORK是如何影响线程的呢?我在这里留个坑,我只是举个例子说明是这个传参的问题。解决办法可以试试用学校机房的老版本虚拟机或者你自己下一个老版本的linux):
我自己的ubuntu和gcc版本为:
我觉得配置很麻烦,博主还是一只备战期末的大二小菜狗,暑假有时间配置一个老版本的试试,hu~
• 在Linux环境中,除pipe和clone公共内存通讯外,还可以采用shm和msg通讯方式,试查阅相关资料了解这些通讯方式,并使用上述任一种方式模拟实现“生产者/消费者”问题。
• 比较Linux系统中pipe、clone、shm和msg四种高级通讯方法的优缺点以及各自适应的环境。
参考博客:shm共享内存
参考博客:msg消息队列
代码:
#include
#include
#include
#include
#include
#include
#define SIZE 1024
int main()
{
int shmid;
char *shmaddr;
struct shmid_ds buf;
int flag = 0;
int pid;
//创建共享内存
shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600);
if ( shmid < 0 )
{
perror("get shm ipc_id error");
return -1;
}
//创建子进程 调用fork会返回两次 父进程返回创建的进程,子进程返回0
pid = fork();
if( pid == 0 )
{//子进程
//将共享内存映射到本地进程
shmaddr = (char *)shmat(shmid, NULL, 0);
if( (int)shmaddr == -1 )
{
perror("shmat addr error");
return -1;
}
strcpy( shmaddr, "Hi,I am child process!\n");
//断开映射
shmdt( shmaddr);
return 0;
}
else if( pid > 0 )
{//父进程
//可能会出现父进程比子进程提前结束执行,所以,延迟等待子进程先执行完
sleep(3);
//得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
flag = shmctl ( shmid, IPC_STAT, &buf);
if( flag == -1 )
{
perror("shmctl shm error");
return -1;
}
printf("shm_segsz=%d bytes\n", buf.shm_segsz);
printf("parent pid=%d,shm_cpid=%d\n",getpid(),buf.shm_cpid);
printf("chlid pid=%d,shm_lpid=%d\n",pid,buf.shm_lpid);
//把共享内存映射到本地
shmaddr = (char *)shmat(shmid, NULL, 0);
if( (int)shmaddr == -1 )
{
perror("shmat addr error");
return -1;
}
printf("%s",shmaddr);
//断开共享内存连接
shmdt(shmaddr);
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
}
else
{
perror("fork error");
shmctl(shmid,IPC_RMID,NULL);
}
return 0;
}