进程管理(2)

Written with StackEdit.

接上一篇

进程(2)

生产者和消费者

计算进程和打印进程

  1. 计算进程生产数据
  2. 打印进程取出数据

通信进程

  1. Send->Producer
  2. Receive->Consumer

生产者和消费者的同步

  • 生产者:
    • 缓冲区满载(sb=0)时等待
    • 放入缓冲区后要发信息
  • 消费者
    • 缓冲区空置(sa=0)时等待
    • 取出数据后发信息.

信号灯设置

  • sb=n;//num of blank buffer
  • sa=0;//num of full buffer
  • mutex=1;//If buffer is being used.1=Free

信号灯描述

生产者的场合:

p(sb);//sb-1
p(mutex);//mutex-1,occupied.
//Put in data...
v(mutes);//Dismiss
v(sa);//num of full data+1

消费者的场合

p(sa);
p(mutex);
//Get out data...
v(mutex);
v(sb);

进程通信

进程之间高效传递大量数据的交互方式.

  • 消息缓存
  • 信箱通信

线程

定义

线程是比进程更小的活动单位,它是进程中的一个执行路径.

  • 进程中的一个执行路径
  • 私用的堆栈和处理执行环境
  • 同一父进程下共享内存
  • 有多个线程存在于同一个进程下.
    特点:
  • 开销小
  • 线程间通信方便,因为有共享内存
  • 可以随时进行创建和销毁
  • 可以加快处理速度

状态变化

Create
Ready
Run
Stop
Wait

并发机制实例

进程的创建

fork();
//@para: None
//@Description:Create a sub process which is a piece of cpoy of the current running  process(EXCEPT this structure called proc).
//@Return:pid
  • 创建分配一个新的pcb
  • 附加标识符pid
  • 逻辑复制父进程的上下文代码.新建数据段和堆栈
  • 继承引用文件索引
  • 返回进程号,对子进程返回0.
  • (子进程不再创建新进程)

Sample

分析进程创建程序的执行情况应该从下面两个可能性入手:

  1. 该代码在父进程中运行,
  2. 在子进程运行
    如上所述,这两种情况的fork()的返回值不一样.

See https://www.geeksforgeeks.org/fork-system-call/ For datails.

#include <stdio.h> 
#include <sys/types.h> 
int main() 
{ 
	fork(); 
	fork(); 
	fork(); 
	printf("hello\n"); 
	return 0; 
} 
  1. 父进程执行第一个fork(),创建一个新进程.
  2. 2个进程各自新创建一个,一共4个
  3. 8个…
  4. 最后有8个Hello World.

还需要注意的是子进程和父进程是同时运行的,所以不能确定哪一个先拿到I/O设备的Access输出.

#include <stdio.h> 
#include <sys/types.h> 
#include <unistd.h> 
void forkexample() 
{ 
	// child process because return value zero 
	if (fork() == 0) 
		printf("Hello from Child!\n"); 

	// parent process because return value non-zero. 
	else
		printf("Hello from Parent!\n"); 
} 
int main() 
{ 
	forkexample(); 
	return 0; 
} 

会有如下两种可能:

1.
Hello from Child!
Hello from Parent!
     (OR)
2.
Hello from Parent!
Hello from Child!

In the above code, a child process is created, fork() returns 0 in the child process and positive integer to the parent process.
Here, two outputs are possible because the parent process and child process are running concurrently. So we don’t know if OS first give control to which process a parent process or a child process.

文件执行,新程序运行

exec(filename,args,environment var);
//@paras:string filename,list args,list environment var
//@Description: Replace process code

More datail See https://www.geeksforgeeks.org/exec-family-of-functions-in-c/
Also: https://www.geeksforgeeks.org/difference-fork-exec/

The exec family of functions replaces the current running process with a new process. It can be used to run a C program by using another C program. It comes under the header file unistd.h. There are many members in the exec family which are shown below with examples.

execvp

Using this command, the created child process does not have to run the same program as the parent process does. The exec type system calls allow a process to run any program files, which include a binary executable or a shell script.

E.g.

//EXEC.c 

#include<stdio.h> 
#include<unistd.h> 

int main() 
{ 
	int i; 
	
	printf("I am EXEC.c called by execvp() "); 
	printf("\n"); 
	
	return 0; 
} 
//execDemo.c 

#include<stdio.h> 
#include<stdlib.h> 
#include<unistd.h> 
int main() 
{ 
		//A null terminated array of character 
		//pointers 
		char *args[]={"./EXEC",NULL}; 
		execvp(args[0],args); 
	
		/*All statements are ignored after execvp() call as this whole 
		process(execDemo.c) is replaced by another process (EXEC.c) 
		*/
		printf("Ending-----"); 
	
	return 0; 
} 

execDemo Called EXEC.exe(compiled from EXEC.c) in code segment, and all statements after this are ignored.

The output is like the following:

I AM EXEC.c called by execvp()

This sentence comes from EXEC.c.

execlp

This also serves the same purpose but the syntax of it are a bit different which is as shown below:

int execlp(const char *file, const char *arg,.../* (char  *) NULL */);

file: file name associated with the file being executed
const char *arg and ellipses : describe a list of one or more pointers to null-terminated strings that represent the argument list available to the executed program.

Difference between fork and exec

See: https://www.geeksforgeeks.org/difference-fork-exec/

Wait&Kill process

See https://www.geeksforgeeks.org/wait-system-call-c/

Wait process to stop

pid_t wait(int *stat_loc)

// C program to demonstrate working of wait() 
#include<stdio.h> 
#include<stdlib.h> 
#include<sys/wait.h> 
#include<unistd.h> 

int main() 
{ 
   pid_t cpid; 
   if (fork()== 0) 
   	exit(0);		 /* terminate child */
   //Note: Just fork and kill child thread
   else
   	cpid = wait(NULL); /* reaping parent */
   printf("Parent pid = %d\n", getpid()); 
   printf("Child pid = %d\n", cpid); 

   return 0; 
} 

wait()returns with child thread’s PID.

参数status_sloc所指向的变量存放子进程的退出码,即从子进程的main函数返回的值或子进程中exit()函数的参数。如果status不是一个空指针,状态信息将被写入它指向的变量。

When wait() returns they also define exit status (which tells our, a process why terminated) via pointer, If status are not NULL.

pid_t waitpid (child_pid, &status, options);

Options Parameter

  • If 0 means no option parent has to wait for terminates child.
  • If WNOHANG means parent does not wait if child does not terminate just check and return waitpid().(not block parent process)
  • If child_pid is -1 then means any arbitrarily child, here waitpid() work same as wait() work.

Return value of waitpid()

  • pid of child, if child has exited
  • 0, if using WNOHANG and child hasn’t exited

用来等待子进程的结束,但它用于等待某个特定进程结束。
参数pid指明要等待的子进程的PID,参数status的含义与wait()函数中的status相同。

Kill process by exit()

exit()
Exit a process with an exit code.

exit() terminates the process normally.
status: Status value returned to the parent process. Generally, a status value of 0 or EXIT_SUCCESS indicates success, and any other value or the constant EXIT_FAILURE is used to indicate an error. exit() performs following operations.

  • Flushes unwritten buffered data.
  • Closes all open files.
  • Removes temporary files.
  • Returns an integer exit status to the operating system

Instance

main() { 
int p1,p2,p3,p4,p5,pp1,pp2;
printf(“程序开始执行”)if ((p1=fork( )== 0{ 
printf(“进程proc1执行”);
exit(1);
}  
else if ((p2=fork() )== 0{
printf(“进程proc2执行”); 
exit(1); 
} 
pp1=wait(&pp1);  /* 等待,直到子进程终止 */ 
pp2=wait(&pp2);  /* 等待,直到子进程终止 */
if ((p3=fork())== 0{ 
printf(“进程proc3执行”); 
} 
else if(p4=fork())== 0{ 
printf(“进程proc4执行”); 
} 
else if(p5=fork())== 0{ 
printf("进程proc5执行"); 
exit(1);
} 
printf(“整个程序终止“)exit(0);
}

Analysis

KN3heP.png

  1. main process creates process1
  2. p1’s fork function returns 0, run and exit.
  3. main creates p2
  4. p2’s fork function returns 0, run and exit.
  5. Wait until two processes exit…
  6. Main creates p3,p4,p5
  7. Run and exit

注意:P3,P4是在最后才退出的,各打印一次All process exit.
注意main进程的存在!

根据进程流图设计程式注意的几点

if (pid=fork()==0)
{
	//process...
	exit(0);
}
else if (pid==forn()==0)
{
	//Another prosess...
	exit(0);
}
wait(NULL);//Or &status
wait(NULL);
  • All process are forked by main process
  • Each process only run itself’s part
  • Wait until all sub processes end

信号量

Linux信号量函数在通用的信号量数组上进行操作,而不是在 一个单一的信号量上进行操作。这些系统调用主要包括: semget、semop和semctl。

创建信号量

int semget(key_t key, int num_sems, int sem_flags)

  • 参数key是一个用来允许多个进程访问相同信号量的整 数值,它们通过相同的key值来调用semget。
  • 参数num_sems参数是所需要的信号量数目。Semget创 建的是一个信号量数组,数组元素的个数即为 num_sems。
  • sem_flags参数是一个标记集合,与open函数的标记十 分类似。低九位是信号的权限,其作用与文件权限类 似。另外,这些标记可以与 IPC_CREAT进行或操作来 创建新的信号量。一般用:IPC_CREAT | 0666

控制信号量

int semctl(int sem_id, int sem_num, int command, ...)

  • 参数sem_id,是由semget所获得的信号量标识符。
  • 参数sem_num参数是信号量数组元素的下标,即指定对第几个信号量进行控制。
  • command参数是要执行的动作,有多个不同的command 值可以用于semctl。常用的两个command值为:SETVAL: 用于为信号量赋初值,其值通过第四个参数指定。 IPC_RMID:当信号量不再需要时用于删除一个信号量标识。

操作信号量

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops)

  • 参数sem_id,是由semget函数所返回的信号量标识符。
  • 参数sem_ops是一个指向结构数组的指针,该结构定义 如下:
  • num_sem_ops 操作次数,一般为1
struct sembuf {
short sem_num; //数组下标
short sem_op; //操作,-1或+1
short sem_flg; //0
}

② P操作

void P(int semid,int index)
{ struct sembuf sem;
sem.sem_num = index;
sem.sem_op = -1;
sem.sem_flg = 0;//:操作标记:0或 IPC_NOWAIT等
semop(semid,&sem,1); //1:表示执行命令的个数
return;
}

③ V操作

void V(int semid,int index)
{ struct sembuf sem;
sem.sem_num = index;
sem.sem_op =  1;
sem.sem_flg = 0;
semop(semid,&sem,1);
return;
}

共享内存

共享内存允许两个或更多进程访问同一块内存,就如同malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。

创建

int shmget(key_t key,int size,int shmflg)

共享内存绑定

int shmget(key_t key,int size,int shmflg)

释放

int shmctl(shmid,cmd,buf);

进程的调度和分配

  1. 调度:在众多处于就绪状态的进程中,按一定的原则选择一个进程.
  2. 分派:当处理机空闲时,移出就绪队列中第一个进程,并赋予它使用处理机的权利.

调度策略:

  • 优先级调度
  • 就绪时间调度

调度方式

系统应对高优先级进程插入时的方式.

  1. 非剥夺方式:等待当前进程运行完毕/阻塞后插入该进程
  2. 剥夺方式:暂停进行进程,直接分配给高优先级进程.

优先级的确定-进程调度算法

静态确定优先级

  • 预计资源
  • 预计运行时间
  • 进程类型

动态确定优先级

  • 进程使用CPU超过一定数值时,降低优先数
  • 进程I/O操作后,增加优先数
  • 进程等待时间超过一定数值时,提高优先数
  • 进程及进程管理——进程调度

循环轮转调度算法

  1. 维护一个就绪进程的pcb队列
  2. 当CPU空闲时,选取就绪队列首元素,赋予一个时间片,当时间片用完时,该进程转为就绪态并进入就绪队列末端.

KNWmAf.png

一个调度用的进程状态变迁图的实例

KNWU4U.png

总结以后再写…XD

发布了80 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/POTASSIUM711/article/details/102722060