linux编程:进程

进程相关概念

程序和进程

        程序,是指编译好的二进制文件,在磁盘上,不占用系统资源(cpu、内存、打开的文件、设备、锁....)

        进程,是一个抽象的概念,与操作系统原理联系紧密。进程是活跃的程序,占用系统资源。在内存中执行。(程序运行起来,产生一个进程)

        程序 → 剧本(纸) 进程 → 戏(舞台、演员、灯光、道具...)

        同一个剧本可以在多个舞台同时上演。同样,同一个程序也可以加载为不同的进程(彼此之间互不影响)

        如:同时开两个终端。各自都有一个bash但彼此ID不同。

并发

        并发,在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但,任一个时刻点上仍只有一个进程在运行。

        例如,当下,我们使用计算机时可以边听音乐边聊天边上网。 若笼统的将他们均看做一个进程的话,为什么可以同时运行呢,因为并发。

分时复用cpu

单道程序设计

        所有进程一个一个排对执行。若A阻塞,B只能等待,即使CPU处于空闲状态。而在人机交互时阻塞的出现时必然的。所有这种模型在系统资源利用上及其不合理,在计算机发展历史上存在不久,大部分便被淘汰了。

多道程序设计

        在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相互穿插的运行。多道程序设计必须有硬件基础作为保证。

        时钟中断即为多道程序设计模型的理论基础。 并发时,任意进程在执行期间都不希望放弃cpu。因此系统需要一种强制让进程让出cpu资源的手段。时钟中断有硬件基础作为保障,对进程而言不可抗拒。 操作系统中的中断处理函数,来负责调度程序执行。

        在多道程序设计模型中,多个进程轮流使用CPU (分时复用CPU资源)。而当下常见CPU为纳秒级,1秒可以执行大约10亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。

1s = 1000ms, 1ms = 1000us, 1us = 1000ns    1000000000

实质上,并发是宏观并行,微观串行! -----推动了计算机蓬勃发展,将人类引入了多媒体时代。

CPU和MMU

中央处理器(CPU)

内存管理单元MMU

进程控制块PCB

        我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。

        /usr/src/linux-headers-3.16.0-30/include/linux/sched.h文件中可以查看struct task_struct 结构体定义。其内部成员有很多,我们重点掌握以下部分即可:

        * 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。

        * 进程的状态,有就绪、运行、挂起、停止等状态。

        * 进程切换时需要保存和恢复的一些CPU寄存器。

        * 描述虚拟地址空间的信息。

        * 描述控制终端的信息。

        * 当前工作目录(Current Working Directory)。

        * umask掩码。

        * 文件描述符表,包含很多指向file结构体的指针。

        * 和信号相关的信息。

        * 用户id和组id。

        * 会话(Session)和进程组。

        * 进程可以使用的资源上限(Resource Limit)。

进程状态

        进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。

环境变量:

环境变量,是指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:

① 字符串(本质) ② 有统一的格式:名=值[:值] ③ 值用来描述进程环境信息。

存储形式:与命令行参数类似。char *[]数组,数组名environ,内部存储字符串,NULL作为哨兵结尾。

使用形式:与命令行参数类似。

加载位置:与命令行参数类似。位于用户区,高于stack的起始位置。

引入环境变量表:须声明环境变量。extern char ** environ; 导入全局的环境变量

例子:打印当前进程的所有环境变量。 

extern char **environ;
int main(void)
{
    int i;
    for (i = 0; environ[i] != NULL; i++)
        printf("%s\n", environ[i]);
    
    return 0;
}

常见环境变量

        按照惯例,环境变量字符串都是name=value这样的形式,大多数name由大写字母加下划线组成,一般把name的部分叫做环境变量,value的部分则是环境变量的值。环境变量定义了进程的运行环境,一些比较重要的环境变量的含义如下:

PATH

        可执行文件的搜索路径。ls命令也是一个程序,执行它不需要提供完整的路径名/bin/ls,然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名./a.out,这是因为PATH环境变量的值里面包含了ls命令所在的目录/bin,却不包含a.out所在的目录。PATH环境变量的值可以包含多个目录,用:号隔开。在Shell中用echo命令可以查看这个环境变量的值:

        $ echo $PATH

SHELL

        当前Shell,它的值通常是/bin/bash。

TERM

        当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。

LANG

        语言和locale,决定了字符编码以及时间、货币等信息的显示格式。

HOME

        当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。

getenv函数

获取环境变量值

    char *getenv(const char *name); 成功:返回环境变量的值;失败:NULL (name不存在)

setenv函数

设置环境变量的值

    int setenv(const char *name, const char *value, int overwrite);   成功:0;失败:-1

参数overwrite取值: 1:覆盖原环境变量

0:不覆盖。(该参数常用于设置新环境变量,如:ABC = haha-day-night)

unsetenv函数

删除环境变量name的定义

    int unsetenv(const char *name); 成功:0;失败:-1

注意事项:name不存在仍返回0(成功),当name命名为"ABC="时则会出错。

int main(void)
{
	char *val;
	const char *name = "ABD";
	val = getenv(name);
	printf("1, %s = %s\n", name, val);
	setenv(name, "haha-day-and-night", 1);
	val = getenv(name);
	printf("2, %s = %s\n", name, val);

#if 1//要执行,0不执行
	int ret = unsetenv("ABD=");
         printf("ret = %d\n", ret);

	val = getenv(name);
	printf("3, %s = %s\n", name, val);

#else
	int ret = unsetenv("ABD");  //name=value:value
	printf("ret = %d\n", ret);

	val = getenv(name);
	printf("3, %s = %s\n", name, val);
#endif
	return 0;}

进程控制

fork函数

创建一个子进程。

        pid_t fork(void); 失败返回-1;成功返回:① 父进程返回子进程的ID(非负) ②子进程返回 0

        pid_t类型表示进程ID,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)

注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。

int var = 34;
int main(void)
{
    pid_t pid;

    pid = fork();
    if (pid == -1 ) {//创建子进程出错
        perror("fork");
        exit(1);
    } else if (pid > 0) {//父进程执行的代码,结束返回子进程pid
        sleep(2);//让父进程后结束,就算就算父进程先抢占到了cpu也会进入阻塞态让子进程先执行
        var = 55;
        printf("I'm parent pid = %d, parentID = %d, var = %d\n", getpid(), getppid(), var);
    } else if (pid == 0) {//子进程执行的代码,结束返回0
        var = 100;
        printf("child  pid = %d, parentID=%d, var = %d\n", getpid(), getppid(), var);
    }
    printf("var = %d\n", var);

    return 0;
}

循环创建n个子进程

        一次fork函数调用可以创建一个子进程。那么创建N个子进程应该怎样实现呢?

        简单想,for(i = 0; i < n; i++) { fork() } 即可。但这样创建的是N个子进程吗?

        子进程创建后又会进入for循环创建子进程的子进程

循环创建N个子进程

        从上图我们可以很清晰的看到,当n为3时候,循环创建了(2^n)-1个子进程,而不是N的子进程。需要在循环的过程,保证子进程不再执行fork ,因此当(fork() == 0)时,子进程应该立即break;才正确。

例子:通过命令行参数指定创建进程的个数,每个进程休眠1S打印自己是第几个被创建的进程。如:第1个子进程休眠0秒打印:“我是第1个子进程”;第2个进程休眠1秒打印:“我是第2个子进程”;第3个进程休眠2秒打印:“我是第3个子进程”。 

通过该练习掌握框架:循环创建n个子进程,使用循环因子i对创建的子进程加以区分。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    int i;
    pid_t pid;
    printf("xxxxxxxxxxx\n");

    for (i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) {
            break;
        }
    }

    if (i < 5) {//子进程退出出口

        sleep(i);
        printf("I'am %d child , pid = %u\n", i+1, getpid());

    } else  {//父进程退出出口
        sleep(i);//假如每次都是父进程抢占到cpu,父进程就会先执行完毕,
                  //加上该语句就会让父进程等待子进程先执行
        printf("I'm parent\n");
    }   
    return 0;
}

getpid函数

        获取当前进程ID

            pid_t getpid(void);

getppid函数

        获取当前进程的父进程ID

           pid_t getppid(void);

区分一个函数是“系统函数”还是“库函数”依据:

        1.是否访问内核数据结构

        2. 是否访问外部硬件资源 二者有任一 → 系统函数;二者均无 → 库函数

getuid函数

获取当前进程实际用户ID

        uid_t getuid(void);

获取当前进程有效用户ID,程序实际执行时享有的权限

        uid_t geteuid(void);

getgid函数

获取当前进程使用用户组ID

        gid_t getgid(void);

获取当前进程有效用户组ID

        gid_t getegid(void);

进程共享

        父子进程之间在fork后。有哪些相同,那些相异之处呢?

        刚fork之后:(0-3G用户空间相同)

        父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...

        父子不同处: 1.进程ID   2.fork返回值   3.父进程ID    4.进程运行时间(父进程从main开始,子进程从fork开始)    5.闹钟(定时器)   6.未决信号集

        似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?

        当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。当子进程对父进程的数据进行读操作时,直接共享父进程的数据,当子进程对父进程进行写操作时直接拷贝一份父进程的数据,然后在进行修改。   

例子:编写程序测试,父子进程是否共享全局变。      

【重点】:父子进程共享:1. 文件描述符(打开文件的结构体)  2. mmap建立的映射区 (进程间通信详解)

特别的,fork之后父进程先执行还是子进程先执行不确定。取决于内核所使用的调度算法。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int a = 100;            //.data 
int main(void)
{
	pid_t pid;
	pid = fork();
	if(pid == 0){	//son
		a = 2000;//子进程只是对自己的全局变量进行修改,不会影响父进程的全局变量
                 //子进程只读全局变量时,会和父进程共享全局变量而不会进行复制,
                //子进程对全局变量进行修改时,子进程会复制一份全局变量进行修改
		printf("child, a = %d\n", a);
	} else {
		sleep(1);	//保证son先运行
		printf("parent, a = %d\n", a);
	}
	return 0;
}

gdb调试

        使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。

        set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。当有多个子进程时就要设置条件断点来跟踪某一个进程。

        set follow-fork-mode parent 设置跟踪父进程。

注意,一定要在fork函数调用之前设置才有效。 

exec函数族

        fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

        将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。

        其实有六种以exec开头的函数,统称exec函数:失败返回-1,成功不返回

        int execl(const char *path, const char *arg, ...);L-list

 execl("/home/itcast/0105_Linux/process_test/exec/output", "output", NULL);从绝对路径当中找到文件,第一个参数为该文件,后面的参数为可执行函数的参数,以NULL结尾。

        int execlp(const char *file, const char *arg, ...);p-path

        execlp("ps", "ps", "ax", NULL);PATH环境变量当中找到可执行文件,第一个参数为可执行函数,后面的参数为可执行函数的参数,以NULL结尾。

        int execle(const char *path, const char *arg, ..., char *const envp[]);e表示环境变量

        int execv(const char *path, char *const argv[]);v-argv

        char* argv[]={“ls”,”-l”,”-a”,NULL};

        execv(“/bin/ls”,argv);path为绝对路径

        int execvp(const char *file, char *const argv[]);

        int execve(const char *path, char *const argv[], char *const envp[]);

execlp函数

        加载一个进程,借助PATH环境变量      

        int execlp(const char *file, const char *arg, ...); 成功:无返回;失败:-1

    参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。

    该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。

        execlp("ls", "ls", "-l", "-a", NULL);文件名,arg[0],arg[1].. ls -l -a,NULL表示结束标志

execl函数,加载一个自定义的程序

        加载一个进程, 通过 路径+程序名 来加载。

            int execl(const char *path, const char *arg, ...); 成功:无返回;失败:-1

对比execlp,如加载"ls"命令带有-l,-F参数

execlp("ls", "ls", "-l", "-F", NULL);      使用程序名在PATH中搜索。

execl("/bin/ls", "ls", "-l", "-F", NULL);    使用参数1给出的绝对路径搜索。

execvp函数

加载一个进程,使用自定义环境变量env

        int execvp(const char *file, const char *argv[]);

变参形式: ①... ② argv[]  (main函数也是变参函数,形式上等同于 int main(int argc, char *argv0, ...))

变参终止条件:① NULL结尾 ② 固参指定

execvp与execlp参数形式不同,原理一致。

例子:将当前系统中的进程信息,打印到文件中。 

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

int main(void)
{
	int fd;
	fd = open("ps.out", O_WRONLY|O_CREAT|O_TRUNC, 0644);
	if(fd < 0){
		perror("open ps.out error");
		exit(1);
	}
	dup2(fd, STDOUT_FILENO);//将输出到屏幕上的重定向输出到文件当中

	execlp("ps", "ps", "aux", NULL);//执行成功后就去执行ps函数而不会返回
	Perror(“exec error”);//失败时才会执行该函数
	Exit(1);
	//close(fd);
	return 0;
}

exec函数族一般规律

        exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。

l (list) 命令行参数列表

p (path) 搜素file时使用path变量

v (vector) 使用命令行参数数组

e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量

        事实上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。

exec函数族

回收子进程

孤儿进程

        孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。Init进程会将子进程进行回收

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

int main(void)
{
    pid_t pid;
    pid = fork();
    if (pid == 0) {//当父进程死亡后子进程变成孤儿进程,孤儿进程的父进程为init进程
        while (1) {
            printf("I am child, my parent pid = %d\n", getppid());
            sleep(1);
        }
    } else if (pid > 0) {
            printf("I am parent, my pid is = %d\n", getpid());
            sleep(9);
            printf("------------parent going to die------------\n");
    } else {
        perror("fork");
        return 1;
    }
    return 0;
}

僵尸进程

        僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。  

        特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。思考!用什么办法可清除掉僵尸进程呢?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
    pid_t pid, wpid;
    pid = fork();
    if (pid == 0) {
            printf("---child, my parent= %d, going to sleep 10s\n", getppid());
            sleep(10);
            printf("-------------child die--------------\n");
    } else if (pid > 0) {//子进程死亡后,父进程没有回收pcb,子进程变成僵尸进程
        while (1) {
            printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
            sleep(1);
        }
    } else {
        perror("fork");
        return 1;
    }
    return 0;
}

wait函数

        一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。

        父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:

             ① 阻塞等待子进程退出

② 回收子进程残留资源

③ 获取子进程结束状态(退出原因)。

      pid_t wait(int *status); 成功:清理掉的子进程ID;失败:-1 (没有子进程),一个wait函数调用只回收一个子进程

        当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)

        可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:传入NULL则不要进程的退出状态

         1.  WIFEXITED(status) 为非0 → 进程正常结束

                WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)

         2. WIFSIGNALED(status) 为非0 → 进程异常终止,kill-l查看终止的信号

                WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。

        *3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态

                WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。

                WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行      

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

int main(void)
{
	pid_t pid, wpid;
	int status;
	pid = fork();
	if(pid == -1){
		perror("fork error");
		exit(1);
	} else if(pid == 0){		//son
		printf("I'm process child, pid = %d\n", getpid());
#if 1
		execl("./abnor", "abnor", NULL);//加载返回错误的程序,利用父进程判断程序错误的原因
		perror("execl error");//子进程程序加载错误才执行
		exit(1);
#endif
		sleep(1);				
		exit(10);
	} else {
		//wpid = wait(NULL);	//传出参数
		wpid = wait(&status);	//传出参数
		if(WIFEXITED(status)){	//正常退出
			printf("I'm parent, The child process "
					"%d exit normally\n", wpid);
			printf("return value:%d\n", WEXITSTATUS(status));//获取进程正常退出值

		} else if (WIFSIGNALED(status)) {	//异常退出
			printf("The child process exit abnormally, "
					"killed by signal %d\n", WTERMSIG(status));
									//获取信号编号
		} else {
			printf("other...\n");
		}
	}
	return 0;
}

int main(void)
{
	char *p = "test of wait abnormally\n";
//    p[0] = 'h';//段错误
    int a = 5/0;//硬件错误,浮点数例外
	return 56;
}

Kill -l 查看所有的错误信号列表

waitpid函数

作用同wait,但可指定pid进程清理,可以不阻塞。

      pid_t waitpid(pid_t pid, int *status, in options); 成功:返回清理掉的子进程ID;失败:-1(无子进程)

        特殊参数和返回情况:

        参数pid: 

                > 0 回收指定ID的子进程

                -1 回收任意子进程(相当于wait)

                0 回收和当前调用waitpid一个组的所有子进程,和-1相似

                < -1 回收指定进程组内的任意子进程,进程组iD加上负号

        返回0:参3为WNOHANG,且子进程正在运行。

        Options参数为0,则进入阻塞等待子进程结束,参数为WNOHANG,不进入阻塞,需要循环判断子进程是否结束,结束后才能进行回收

注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。

例子:父进程fork 3 个子进程,三个子进程一个调用ps命令, 一个调用自定义程序1(正常),一个调用自定义程序2(会出段错误)。父进程使用waitpid对其子进程进行回收。

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

int main(int argc, char *argv[])
{
	int n = 5, i;				
         pid_t p, q;
	if(argc == 2){	
	    n = atoi(argv[1]);
	}

	for(i = 0; i < n; i++)	 {
           p = fork();
	  if(p == 0) {
	    break;			
          } else if (i == 3){
            q = p;
         }
       }
	if(n == i){  // parent
	   sleep(n);//让子进程都先于父进程结束
	   printf("I am parent, pid = %d\n", getpid());      
            p = waitpid(q, NULL,0);//指定回收某个进程,当父进程结束后,子进程会有init进程对其进行回收
       // while(waitpid(-1,NULL,0);//循环回收所有的子进程
       /* 
        do{
            wpid = waitpid(-1,NULL,WNOHANG);//还回零则子进程在运行,返回非零则回收该子进程
            if (wpid > 0)//回收一个子进程n就减少1             
                n--;
            sleep(1);
        }while (n>0);
        printf("wait finish\n");
        */    
	} else {
		sleep(i);
		printf("I'm %dth child, pid = %d\n", i+1, getpid());
	}
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
进程间通信是指在不同进程之间进行数据交换和同步的一种技术。Linux提供了多种进程间通信的方式,包括管道、消息队列、共享内存和信号量等。在实验六中,我们将学习如何使用这些方式进行进程间通信。 1. 管道 管道是一种半双工的通信方式,它可以在两个进程之间传递数据。在Linux中,管道分为匿名管道和命名管道。匿名管道只能用于父子进程之间的通信,而命名管道可以用于任意两个进程之间的通信。 使用匿名管道进行进程间通信的步骤如下: - 父进程创建管道,并调用fork函数创建子进程。 - 子进程通过管道接收数据。 - 父进程通过管道发送数据。 - 子进程接收到数据后进行处理。 使用命名管道进行进程间通信的步骤如下: - 创建命名管道。 - 打开命名管道并进行读写操作。 2. 消息队列 消息队列是一种进程间通信机制,它允许不同进程之间通过一个消息传递序列来进行通信。在Linux中,每个消息都有一个类型,接收进程可以选择接收某个特定类型的消息。 使用消息队列进行进程间通信的步骤如下: - 创建消息队列。 - 发送消息到消息队列。 - 接收消息并进行处理。 3. 共享内存 共享内存是一种进程间通信的方式,它允许不同进程之间共享同一个物理内存区域。这种方式比较高效,但需要考虑进程间的同步和互斥问题,否则会出现数据不一致的情况。 使用共享内存进行进程间通信的步骤如下: - 创建共享内存区域。 - 进程通过共享内存区域进行数据交换。 - 进程需要进行同步和互斥操作。 4. 信号量 信号量是一种进程间同步的机制,它可以用来保证不同进程之间的共享资源在同一时刻只能被一个进程访问。在Linux中,每个信号量都有一个计数器,当计数器为0时,进程需要等待;当计数器大于0时,进程可以继续执行。 使用信号量进行进程间通信的步骤如下: - 创建信号量。 - 进程对信号量进行P操作(等待)。 - 进程对信号量进行V操作(释放)。 总体来说,不同的进程间通信方式各有优缺点,应根据实际需求选择适合的方式。在实验六中,我们将通过编写代码来学习如何使用这些方式进行进程间通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值