多任务处理
一、进程
1、简介
/*
进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元;
程序是放到磁盘的可执行文件
进程是指程序执行的实例
进程与程序:
进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行。通常进程不可在计算机之间迁移;而程序通常对应着文件、静态和可以复制
进程是暂时的,程序是长久的:进程是一个状态变化的过程,程序可长久保存
进程与程序组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)
进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。
进程的生命周期
创建:每个进程都是由其父进程创建进程可以创建子进程,子进程又可以创建子进程的子进程
运行:多个进程可以同时存在进程间可以通信
撤销:进程可以被撤销,从而结束一个进程的运行
进程的状态
执行状态:进程正在占用CPU
就绪状态:进程已具备一切条件,正在等待分配CPU的处理时间片
等待状态:进程不能使用CPU,若等待事件发生则可将其唤醒
*/
/*
Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任。其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,它也不会影响到系统中的其他进程。
Linux中的进程包含3个段,分别为“数据段”、“代码段”和“堆栈段”。
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间;
“代码段”存放的是程序代码的数据。
“堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量等。
*/
/*
进程ID(PID):标识进程的唯一数字
父进程的ID(PPID)
启动进程的用户ID(UID)
进程互斥:指当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用该资源的进程必须等待,直到占用该资源者释放了该资源为止
临界资源:操作系统中将一次只允许一个进程访问的资源称为临界资源
临界区:进程中访问临界资源的那段程序代码称为临界区,为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区
进程同步:一组并发进程按一定的顺序执行的过程称为进程间的同步
具有同步关系一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件
进程调度:按一定算法,从一组待运行的进程中选出一个来占有CPU运行。
分为抢占式和非抢占式
调度算法:
先来先服务调度算法
短进程优先调度算法
高优先级优先调度算法
时间片轮转法
死锁:多个进程因竞争资源而形成一种僵局若无外力作用,这些进程都将永远不能再向前推进
*/
1、创建
1.1 fork
pid_t fork(void)
/*
功能:创建子进程
fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:
返回给父进程的值为子进程id
返回给子进程的值为0
返回值为-1表示创建子进程失败,无子进程
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, char const *argv[])
{
pid_t pid;
int count=0;
pid=fork();
if(pid<0){
perror("fork error!:");
exit(-1);
}
else if(pid>0){
count++;
printf("pid = %d \t",getpid());
printf("count = %d \n",count);
}
else{
count++;
printf("pid = %d \t",getpid());
printf("count = %d \n",count);
}
return 0;
}
/*
执行结果为:
pid = 19585 count = 1
pid = 19586 count = 1
*/
1.2 vfork
pid_t vfork(void)
/*
功能:创建子进程
返回给父进程的值为子进程id
返回给子进程的值为0
返回值为-1表示创建子进程失败,无子进程
在使用vfork时,子进程先执行,执行到return会将局部变量释放,从而导致父进程中的局部变量被释放。
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, char const *argv[])
{
pid_t pid;
int count=0;
pid=vfork();
if(pid<0){
perror("fork error!:");
exit(-1);
}
else if(pid>0){
count++;
printf("count = %d \n",count);
}
else{
count++;
printf("count = %d \n",count);
_exit(0);//结束进程(系统调用,不会清空文件缓冲区、包含在unistd.h中)
}
//exit(0);//结束进程(c库函数实现、包含在stdlib.h中 不会释放变量,会清空文件缓冲区)
return 0;
}
/*
执行结果为:
count = 1
count = 2
*/
1.3 fork 与 vfork
/*
1. fork:子进程拷贝父进程的数据段
vfork:子进程与父进程共享数据段
2. fork:父、子进程的执行次序不确定
vfork:子进程先运行,父进程后运行
*/
2、退出
/*
*/
3、等待
/*
父进程等子进程
一个进程结束以后,其父进程也结束,此进程为孤儿进程。
一个进程结束以后,其父进程未结束,可能形成僵尸进程。
僵尸进程占用系统资源,需要父进程清除。
*/
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int * status)
/*
功能:阻塞该进程,直到其某个子进程退出。
*/
pid_t waitpid (pid_t pid,int * status,int options)
waitpid(-1,?,0)此时waitpid与wait相同
/*
pid:需要阻塞的进程id
options:设置阻塞或不阻塞
0:阻塞等待
WNOHANG:非阻塞
*/
4、exec(函数簇)
#include<unistd.h>
/*
exec用 被执行的程序 替换 调用它的程序。
区别:fork创建一个新的进程,产生一个新的PID。
exec启动一个新程序,替换原有的进程,因此进程的PID不会改变
*/
int execl(const char * path,const char * arg1, ....)
/*
path:被执行程序名(含完整路径)。
arg1-argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
*/
int execlp(const char * filename,const char * arg1, ....)
/*
filename:去默认路径(标准搜索目录)搜索该文件,若搜索不到,则执行出错
*/
int execle(const char * path,const char * arg1, ....,char *env[])
/*
filename:去默认路径(标准搜索目录)搜索该文件,若搜索不到,则执行出错
env:环境变量
*/
int execv(const char * path, char * const argv[ ])
/*
path:被执行程序名(含完整路径)。
argv[]: 被执行程序所需的命令行参数数组。
*/
int execvp(const char * filename,char * const argv[ ])
/*
filename:去默认路径(标准搜索目录)搜索该文件,若搜索不到,则执行出错
*/
int execve(const char * path,char * const argv[ ],char *env[])
/*
filename:去默认路径(标准搜索目录)搜索该文件,若搜索不到,则执行出错
env:环境变量
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid=fork();
if(pid<0){
perror("fork error!");
exit(-1);
}
else if(pid>0){
int i=0;
while(i<3){
printf("this is in parent process!\n");
sleep(1);
i++;
}
}else{
char *ch[]={"main","hello","world",NULL};
//execl("/bin/ls","ls","-al","/home/jsetc/suqian",NULL);
//execl("./main","main","hello","world",NULL);
execv("./main",ch);
//printf("2\n");
}
//exit(0);
return 0;
}
#include <stdlib.h>
int system( const char* string )
/*
功能:调用fork产生子进程,由子进程来调用/bin/sh -c string来执行参数string所代表的命令
相比于exec,此函数后续内容会继续执行
返回值:
-1,调用出错
==0,调用命令成功,但没有创建子进程
>0,所创建的子进程的id
*/
二、进程通信
简介
/*
1.数据传输
一个进程需要将它的数据发送给另一个进程
2.资源共享
多个进程之间共享同样的资源
3.通知事件
一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件
4.进程控制
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变
*/
/*
现在Linux使用的进程间通信方式包括:
1、管道(pipe)和有名管道(FIFO)
2、信号(signal)
3、消息队列
4、共享内存
5、信号量
6、套接字(socket)
*/
1、管道
/*
管道是单向的、先进先出的,半双工,它把一个进程的输出和另一个进程的输入连接在一起。
一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据
数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。
管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞
管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
先进先出
随内核存在,无法保存
管道由fork出的子进程继承
无名管道只能在具有亲缘关系的进程间通信
管道用于不同进程间通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道
必须在系统调用fork( )前调用pipe( ),否则子进程将不会继承文件描述符
#include<unistd.h>
*/
int pipe(int filedis[2]);
/*
功能:创建一个管道。当一个管道建立时,它会创建两个文件描述符:filedis[0] 用于读管道, filedis[1] 用于写管道
*/
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode)
/*
pathname:FIFO文件名
mode:属性
一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO
*/
//使用无名管道实现字符串读写
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include <sys/wait.h>
#include<string.h>
#include<stdlib.h>
int main(int argc, char const *argv[])
{
int fd[2];
int fd2[2];
pid_t pid;
char r_buf[6];
char w_buf[10];
int i;
int cmd;
int childexit=0;
memset(r_buf,0,sizeof(r_buf));
if(pipe(fd)<0)
{
perror("create pipe error!");
exit(-1);
}
else
{
printf("create pipe successfully!\n");
}
if(pipe(fd2)<0)
{
perror("create pipe error!");
exit(-1);
}
else
{
printf("create pipe2 successfully!\n");
}
if((pid=fork())<0)
{
perror("fork error!");
close(fd[0]);
close(fd[1]);
close(fd2[0]);
close(fd2[1]);
exit(-1);
}
else if (0==pid)
{
close(fd[1]);
close(fd2[0]);
read(fd[0],r_buf,6);
printf("r_buf:%s\n",r_buf);
for(int j=0;r_buf[j];j++){
r_buf[j]-=32;
}
write(fd2[1],r_buf,6);
close(fd[0]);
close(fd2[1]);
_exit(0);
}
else
{
close(fd[0]);
close(fd2[1]);
write(fd[1],"hello",6);
read(fd2[0],w_buf,6);
printf("w_buf:%s\n",w_buf);
close(fd[1]);
close(fd2[0]);
}
wait(NULL);
return 0;
}
//使用有名管道实现读写
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
#define FIFO "./1"
int main(int argc, char const *argv[])
{
int fd;
pid_t pid;
if(mkfifo(FIFO,0666)<0)
{
perror("create pipe error!");
exit(-1);
}
else
{
printf("create pipe successfully!\n");
}
if((pid=fork())<0)
{
perror("fork error!");
exit(-1);
}
else if (0==pid)
{
char s[6]="0";
fd=open(FIFO,O_RDONLY);
read(fd,s,6);
puts(s);
close(fd);
}
else
{
fd=open(FIFO,O_WRONLY);
write(fd,"hello",6);
close(fd);
}
return 0;
}
3、信号
/*
所有进程通信方式中唯一一个异步通信方式
信号(signal)机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:
1、当用户按某些按键时,产生信号
2、硬件异常产生信号:
除数为0、无效的存储访问等等。
这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,
例如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号
3、进程用kill函数将信号发送给另一个进程
4、用户可用kill命令将信号发送给其他进程
下面是几种常见的信号:
SIGHUP: 从终端上发出的结束信号
SIGINT: 来自键盘的中断信号(Ctrl-C)
SIGKILL:该信号结束接收信号的进程
SIGTERM:kill 命令发出的信号
SIGCHLD:标识子进程停止或结束的信号
SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号
*/
/*
当某信号出现时,将按照下列三种方式中的一种进行处理:
1、忽略此信号
大多数信号都按照这种方式进行处理,但有两种信号却决不能被忽略,它们是:SIGKILL\SIGSTOP。
这两种信号不能被忽略的原因是:它们向超级用户提供了一种终止或停止进程的方法
2、执行用户希望的动作
通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理
3、执行系统默认动作
对大多数信号的系统默认动作是终止该进程
*/
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo)
int raise(int signo)
/*
kill既可以向自身发送信号,也可以向其他进程发送信号。
与kill函数不同的是,raise函数是向进程自身发送信号
kill的pid参数有四种不同的情况:
1、pid > 0
将信号发送给进程ID为pid的进程。
2、pid == 0
将信号发送给同组的进程。
3、pid < -1
将信号发送给其进程组ID等于pid绝对值的进程。
4、pid == -1
将信号发送给所有进程。
返回值:
-1发送信号失败
0发送成功
*/
#include<unistd.h>
unsigned int alarm(unsigned int seconds)
/*
Seconds:经过了指定的seconds秒后会产生信号SIGALRM。
每个进程只能有一个闹钟时间.如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换
如果有以前登记的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟
第二次alarm的返回值是前一次alarm剩余的时间
使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGALRM信号.如果不捕捉此信号,则默认动作是终止该进程
*/
int pause(void)
/*
pause函数使调用进程挂起直至捕捉到一个信号。只有执行了一个信号处理函数后,挂起才结束
*/
/*
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
pid_t pid;
int ret;
if((pid=fork())<0){
perror("fork error!");
exit(-1);
}
else if(0==pid){
raise(SIGSTOP);
exit(0);
}
else{
printf("pid=%d\n",pid);
if(waitpid(pid,NULL,WNOHANG)==0){
//if(waitpid(pid,NULL,0)==0){
if((ret=kill(pid,SIGKILL))==0){
printf("killed %d\n",pid);
}
else{
perror("kill error!");
}
}
}
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc, char const *argv[])
{
int ret;
ret=alarm(5);
pause();
printf("i have been wake up!\n");
return 0;
}