学习笔记 day18 系统编程:进程及进程间通信

多任务处理

一、进程

1、简介

/*
进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元;

程序是放到磁盘的可执行文件
进程是指程序执行的实例
进程与程序:
    进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行。通常进程不可在计算机之间迁移;而程序通常对应着文件、静态和可以复制
    进程是暂时的,程序是长久的:进程是一个状态变化的过程,程序可长久保存
    进程与程序组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)
    进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。

进程的生命周期
	创建:每个进程都是由其父进程创建进程可以创建子进程,子进程又可以创建子进程的子进程
	运行:多个进程可以同时存在进程间可以通信
	撤销:进程可以被撤销,从而结束一个进程的运行
	
进程的状态
	执行状态:进程正在占用CPU
    就绪状态:进程已具备一切条件,正在等待分配CPU的处理时间片
    等待状态:进程不能使用CPU,若等待事件发生则可将其唤醒
*/
image-20230112112247228
/*
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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛奶奥利奥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值