运用 Linux 实现进程间传递数据技巧 (linux 给进程传递数据)

在 Linux 系统中,进程间通信(IPC)是非常重要的一部分。进程间通信指的是不同进程之间进行数据交换的过程。进程间传递数据技巧是一种重要的进程通信方法,通过它可以实现进程之间的数据传递,使进程之间的数据交换更加高效、安全和可靠。下面,我们就来介绍一下如何。

一、Linux 下的进程间传递数据技巧简介

Linux 下的进程间传递数据技巧主要包括管道、消息队列、共享内存和信号等。它们都是使用进程间通信技术实现的,但是具体实现方式有所不同。下面,我们来逐一介绍这几种技巧。

1. 管道

管道是一种单向通信方式,其本质上是一个缓冲区,可以用来实现进程之间的数据传输。管道分为匿名管道和命名管道两种方式。

(1)匿名管道:只存在于创建它的进程和它创建的子进程之间,可以用于进程间的简单通信。

(2)命名管道:存在于文件系统中,可以被多个进程以只读或只写的方式打开,用于进程间的复杂通信。

2. 消息队列

消息队列是一种常用的进程间通信机制,其目的是允许不同进程之间交换数据,克服了管道只能传输无格式字节流数据的限制。使用消息队列传输消息时,其内部是通过一个缓冲队列来实现的,支持多个进程之间的消息传输。

3. 共享内存

共享内存是一种高速的进程间通信方式。使用共享内存传输数据时,其内部是通过将同一个物理内存映射到多个进程的虚拟地址空间中来实现的,多个进程可以共享这个内存区域,以实现实时数据传输。

4. 信号

信号是 Linux 下的一种基本进程间通信机制。它是 Linux 内核和应用程序之间进行通信的主要方式之一,通过向进程发送信号来通知它们发生了某些特定的事件。常见的信号包括进程终止信号、中断信号和定时器信号等。

二、Linux 下进程间传递数据技巧的应用场景

Linux 下的进程间传递数据技巧可以应用于多种场景,如网络通信、多线程编程以及分布式系统等方面。下面我们来逐一介绍这些场景。

1. 网络通信

在网络通信中,进程间通信技术是必不可少的。使用进程间通信技术可以实现进程之间的数据传递,使得进程之间的数据交换更加高效、安全和可靠。常见的应用场景包括服务器端程序、客户端程序以及网络协议栈等。

2. 多线程编程

在多线程编程中,多个线程之间需要进行数据共享和协调。使用进程间通信技术可以实现多个线程之间的数据交换和消息传递,以实现线程之间的协作和数据同步。常见的应用场景包括多线程数据处理、线程池管理以及多线程任务队列等。

3. 分布式系统

在分布式系统中,进程间通信技术是构建分布式系统的核心技术之一。使用进程间通信技术可以实现不同计算机节点之间的数据共享和消息传递,以实现分布式数据处理和协调。常见的应用场景包括分布式计算、分布式数据库以及分布式存储等。

三、Linux 下进程间传递数据技巧的实现

在 Linux 系统中,实现进程间传递数据技巧需要使用到相应的系统调用和库函数。以下是常见的几种实现方法。

1. 管道

在 Linux 系统中,可以使用 pipe 系统调用创建匿名管道,并使用 read 和 write 系统调用在两个进程之间传递数据。命名管道可以通过 mkfifo 系统调用创建,进程可以通过文件 I/O 函数来进行读写操作。

2. 消息队列

在 Linux 系统中,可以使用 msgget 系统调用来创建消息队列,并使用 msgsnd 和 msgrcv 系统调用在进程之间传递消息。msgget 系统调用用于创建或访问一个已经存在的消息队列,msgsnd 系统调用用于向消息队列发送消息,msgrcv 系统调用用于从消息队列中接收消息。

3. 共享内存

在 Linux 系统中,可以使用 shmget 系统调用创建共享内存区域,并使用 shmat 和 shmdt 系统调用将共享内存区域映射到进程的虚拟地址空间中。shmget 系统调用用于创建或访问一个共享内存区域,shmat 系统调用用于将共享内存区域映射到进程的虚拟地址空间中,shmdt 系统调用用于取消共享内存区域的映射。

4. 信号

在 Linux 系统中,可以使用 kill 系统调用向指定的进程发送信号。kill 系统调用可以向指定的进程发送任意用户定义的信号,例如进程终止信号、中断信号和定时器信号等。进程可以通过信号处理函数来响应接收到的信号。

四、

是一种非常重要的通信技术,可以用于各种应用场景,如网络通信、多线程编程以及分布式系统等。在 Linux 系统中,管道、消息队列、共享内存和信号都是常用的进程通信机制。通过合理的选用和使用这些技巧,可以实现进程间的高效、安全和可靠的数据传递。因此,对于 Linux 系统开发人员来说,熟练使用进程间传递数据技巧是必备的技能之一。

相关问题拓展阅读:

在linux编程中若一个用户程序希望将一组数据传递给kernel有几种方式

教科书里的Linux代码例子都已作古,所以看到的代码不能当真,领会意思就行了

比如以前的init进程的启动代码

execve(init_filename,argv_init,envp_init);

现在改为

static void run_init_process(char *init_filename)

{

argv_init = init_filename;

kernel_execve(init_filename, argv_init, envp_init);

}

好的,聪明人就发现,linux内核中调用用户空间的程序可以使用init这样的方式,调用 kernel_execve

不过内核还是提供了更好的辅助接口call_usermodehelper,自然最后也是调用kernel_execve

调用特定的内核函数(系统调用)是 GNU/Linux 中软件开发的原本就有的组成部分。但如果方向反过来呢,内核空间调用用户空间?确实有一些有这种特性的应用程序需要每天使用。例如,当内核找到一个设备, 这时需要加载某个模块,进程如何处理?动态模块加载在内核通过 usermode-helper 进程进行。

让我们从探索 usermode-helper 应用程序编程接口(API)以及在内核中使用的例子开始。 然后,使用 API 构造一个示例应用程序,以便更好地理解其工作原理与局限。

usermode-helper API

usermode-helper API 是个很简单的 API,其选项为用户熟知。例如,要创建一个用户空间进程,通常只要设置名称为 executable,选项都为 executable,以及一组环境变量(指向 execve 主页)念桥。创建内核进程也是一样。但由于创建内核空间进程,还需要设置一些额外选项。

内核版本

本文探讨的是 2.6.27 版内核的 usermode-helper API。

表 1 展示的是 usermode-helper API 中一组关键的内核函数

表 1. usermode-helper API 中的核心函数

API 函数

描述

call_usermodehelper_setup 准备 user-land 调用的处理函数

call_usermodehelper_setkeys 设置 helper 的会话密钥

call_usermodehelper_setcleanup 为 helper 设置一个清空函数

call_usermodehelper_stdinpipe 为 helper 创建 stdin 管道

call_usermodehelper_exec 调用 user-land

表 2 中还有一些简化函仔嫌猛数,它们封装了的几个内者斗核函数(用一个调用代替多个调用)。这些简化函数在很多情况下都很有用,因此尽可能使用他们。

表 2. usermode-helper API 的简化

API 函数

描述

call_usermodehelper 调用 user-land

call_usermodehelper_pipe 使用 stdin 管道调用 user-land

call_usermodehelper_keys 使用会话密钥调用 user-land

让我们先浏览一遍这些核心函数,然后探索简化函数提供了哪些功能。核心 API 使用了一个称为subprocess_info 结构的处理函数引用进行操作。该结构(可在 ./kernel/kmod.c 中找到)了给定的 usermode-helper 实例的所有必需元素。该结构引用从 call_usermodehelper_setup 调用返回。该结构(以及后续调用)将会在 call_usermodehelper_setkeys(用于存储凭证)、call_usermodehelper_setcleanup 以及 call_usermodehelper_stdinpipe 的调用中进一步配置。最后,一旦配置完成,就可通过调用 call_usermodehelper_exec 来调用配置好的用户模式应用程序。

声明

该方法提供了一个从内核调用用户空间应用程序必需的函数。尽管这项功能有合理用途,还应仔细考虑是否需要其他实现。这是一个方法,但其他方法会更合适。

核心函数提供了更大程度的控制,其中 helper 函数在单个调用中完成了大部分工作。管道相关调用(call_usermodehelper_stdinpipe 和 helper 函数 call_usermodehelper_pipe)创建了一个相联管道供 helper 使用。具体地说,创建了管道(内核中的文件结构)。用户空间应用程序对管道可读,内核对管道可写。对于本文,核心转储只是使用 usermode-helper 管道的应用程序。在该应用程序(./fs/exec.c do_coredump())中,核心转储通过管道从内核空间写到用户空间。

这些函数与 sub_processinfo 以及 subprocess_info 结构的细节之间的关系如图 1 所示。

图 1. Usermode-helper API 关系

表 2 中的简化函数内部执行 call_usermodehelper_setup 函数和 call_usermodehelper_exec 函数。表 2 中最后两个调用分别调用的是 call_usermodehelper_setkeys 和 call_usermodehelper_stdinpipe。可以在 ./kernel/kmod.c 找到 call_usermodehelper_pipe 和 call_usermodehelper 的代码,在 ./include/linux/kmod.h 中找到 call_usermodhelper_keys 的代码。

为什么要从内核调用用户空间应用程序?

现在让我们看一看 usermode-helper API 所使用的内核空间。表 3 提供的并不是专门的应用程序列表,而是一些有趣应用的示例。

表 3. 内核中的 usermode-helper API 应用程序

应用程序

源文件位置

内核模块调用 ./kernel/kmod.c

电源管理 ./kernel/sys.c

控制组 ./kernel/cgroup.c

安全密匙生成 ./security/keys/request_key.c

内核事件交付 ./lib/kobject_uevent.c

最直接的 usermode-helper API 应用程序是从内核空间加载内核模块。request_module 函数封装了 usermode-helper API 的功能并提供了简单的接口。在一个常用的模块中,内核指定一个设备或所需服务并调用 request_module 来加载模块。通过使用 usermode-helper API,模块通过 modprobe 加载到内核(应用程序通过 request_module 在用户空间被调用)。

与模块加载类似的应用程序是设备热插拔(在运行时添加或删除设备)。该特性是通过使用 usermode-helper API,调用用户空间的 /in/hotplug 工具实现的。

关于 usermode-helper API 的一个有趣的应用程序(通过 request_module) 是文本搜索 API(./lib/textsearch.c)。该应用程序在内核中提供了一个可配置的文本搜索基础架构。该应用程序使用 usermode-helper API 将搜索算法当作可加载模块进行动态加载。在 2.6.30 内核版本中,支持三个算法,包括 Boyer-Moore(./lib/ts_bm.c),简单固定状态机方法(./lib/ts_f.c),以及 Knuth-Morris-Pratt 算法(./lib/ts_kmp.c)。

usermode-helper API 还支持 Linux 按照顺序关闭系统。当需要系统关闭电源时,内核调用用户空间的 /in/poweroff 命令来完成。其他应用程序如 表 3 所示,表中附有其源文件位置。

Usermode-helper API 内部

在 kernel/kmod.c 中可以找到 usermode-helper API 的源代码 和 API(展示了主要的用作内核空间的内核模块加载器)。这个实现使用 kernel_execve 完成脏工作(dirty work)。请注意 kernel_execve是在启动时开启 init 进程的函数,而且未使用 usermode-helper API。

usermode-helper API 的实现相当简单直观(见图 2)。usermode-helper 从调用call_usermodehelper_exec 开始执行(它用于从预先配置好的 subprocess_info 结构中清除用户空间应用程序)。该函数接受两个参数:subprocess_info 结构引用和一个枚举类型(不等待、等待进程中止及等待进程完全结束)。subprocess_info(或者是,该结构的 work_struct 元素)然后被压入工作队列(khelper_wq),然后队列异步执行调用。

图 2. usermode-helper API 内部实现

当一个元素放入 khelper_wq 时,工作队列的处理函数就被调用(本例中是__call_usermodehelper),它在 khelper 线程中运行。该函数从将 subprocess_info 结构出队开始,此结构包含所有用户空间调用所需信息。该路径下一步取决于 wait 枚举变量。如果请求者想要等整个进程结束,包含用户空间调用(UMH_WAIT_PROC)或者是根本不等待(UMH_NO_WAIT),那么会从 wait_for_helper 函数创建一个内核线程。否则,请求者只是等待用户空间应用程序被调用(UMH_WAIT_EXEC),但并不完全。这种情况下,会为____call_usermodehelper() 创建一个内核线程。

在 wait_for_helper 线程中,会安装一个 SIGCHLD 信号处理函数,并为 ____call_usermodehelper 创建另一个内核线程。但在 wait_for_helper 线程中,会调用 sys_wait4 来等待____call_usermodehelper 内核线程(由 SIGCHLD 信号指示)结束。然后线程执行必要的清除工作(为UMH_NO_WAIT 释放结构空间或简单地向 call_usermodehelper_exec() 回送一个完成报告)。

函数 ____call_usermodehelper 是实际让应用程序在用户空间启动的地方。该函数首先解锁所有信号并设置会话密钥环。它还安装了 stdin 管道(如果有请求)。进行了一些安装以后,用户空间应用程序通过 kernel_execve(来自 kernel/syscall.c)被调用,此文件包含此前定义的 path、argv 清单(包含用户空间应用程序名称)以及环境。当该进程完成后,此线程通过调用 do_exit() 而产生。

该进程还使用了 Linux 的 completion,它是像信号一样的操作。当 call_usermodehelper_exec 函数被调用后,就会声明 completion。当 subprocess_info 结构放入 khelper_wq 后,会调用wait_for_completion(使用 completion 变量作为参数)。请注意此变量会存储到 subprocess_info 结构作为 complete 字段。当子线程想要唤醒 call_usermodehelper_exec 函数,会调用内核方法complete,并判断来自 subprocess_info 结构的 completion 变量。该调用会解锁此函数使其能继续。可以在 include/linux/completion.h 中找到 API 的实现。

应用程序示例

现在,让我们看看 usermode-helper API 的简单应用。首先看一下标准 API,然后学习如何使用 helper 函数使事情更简单。

在该例中,首先开发了一个简单的调用 API 的可加载内核模块。清单 1 展示了样板模块功能,定义了模块入口和出口函数。这两个函数根据模块的 modprobe(模块入口函数)或 inod(模块入口函数),以及 rmmod(模块出口函数)被调用。

清单 1. 模块样板函数

#include

#include

#include

MODULE_LICENSE( “GPL” );

static int __init mod_entry_func( void )

{

return umh_test();

}

static void __exit mod_exit_func( void )

{

return;

}

module_init( mod_entry_func );

module_exit( mod_exit_func );

usermode-helper API 的使用如 清单 2 所示,其中有详细描述。函数开始是声明所需变量和结构。以subprocess_info 结构开始,它包含所有的执行用户空间调用的信息。该调用在调用call_usermodehelper_setup 时初始化。下一步,定义参数列表,使 argv 被调用。该列表与普通 C 程序中的 argv 列表类似,定义了应用程序(数组之一个元素)和参数列表。需要 NULL 终止符来提示列表末尾。请注意这里的 argc 变量(参数数量)是隐式的,因为 argv 列表的长度已经知道。该例中,应用程序名是 /usr/bin/logger,参数是 help!,然后是 NULL 终止符。下一个所需变量是环境数组(envp)。该数组是一组定义用户空间应用程序执行环境的参数列表。本例中,定义一些常用的参数,这些参数用于定义 shell 并以 NULL 条目结束。

清单 2. 简单的 usermode_helper API 测试

static int umh_test( void )

{

struct subprocess_info *sub_info;

char *argv = { “/usr/bin/logger”, “help!”, NULL };

static char *envp = {

“HOME=/”,

“TERM=linux”,

“PATH=/in:/bin:/usr/in:/usr/bin”, NULL };

sub_info = call_usermodehelper_setup( argv, argv, envp, GFP_ATOMIC );

if (sub_info == NULL) return -ENOMEM;

return call_usermodehelper_exec( sub_info, UMH_WAIT_PROC );

}

下一步,调用 call_usermodehelper_setup 来创建已初始化的 subprocess_info 结构。请注意使用了先前初始化的变量以及指示用于内存初始化的 GFP 屏蔽第四个参数。在安装函数内部,调用了kzalloc(分配内核内存并清零)。该函数需要 GFP_ATOMIC 或 GFP_KERNEL 标志(前者定义调用不可以休眠,后者定义可以休眠)。快速测试新结构(即,非 NULL)后,使用 call_usermodehelper_exec 函数继续调用。该函数使用 subprocess_info 结构以及定义是否等待的枚举变量(在 “Usermode-helper API 内部” 一节中有描述)。全部完成! 模块一旦加载,就可以在 /var/log/messages 文件中看到信息。

还可以通过 call_usermodehelper API 函数进一步简化进程,它同时执行 call_usermodehelper_setup和 call_usermodehelper_exec 函数。如清单 3 所示,它不仅删除函数,还消除了调用者管理subprocess_info 结构的必要性。

清单 3. 更简单的 usermode-helper API 测试

static int umh_test( void )

{

char *argv = { “/usr/bin/logger”, “help!”, NULL };

static char *envp = {

“HOME=/”,

“TERM=linux”,

“PATH=/in:/bin:/usr/in:/usr/bin”, NULL };

return call_usermodehelper( argv, argv, envp, UMH_WAIT_PROC );

}

架构师进阶:Linux进程间如何共享内存

共享内存 IPC 原理

共享内存进程间通信机制主要用于实现进程间大量的数据传输,下图所示为进程间使用芹汪共享内存实现大量数据传输的示意图:

640

共享内存是在内存中单独开辟的一段内存空间,这段内存空间有自己特有的数据结构,包括访问权限、大小和最近访问的时间等。该数据结构定义如下:

from /usr/include/linux/shm.h

struct shmid_ds {

struct ipc_perm shm_perm; /* operation perms 操作权限 */

int shm_segsz; /* size of segment (bytes) 段长度大小 */

__kernel_time_t shm_atime; /* last attach time 最近attach时间 */

__kernel_time_t shm_dtime; /* last detach time 最近detach时间 */

__kernel_time_t shm_ctime; /* last change time 最近change时间 */

__kernel_ipc_pid_t shm_cpid; /* pid of creator 创建者pid */

__kernel_ipc_pid_t shm_lpid; /* pid of last operator 最近操作pid */

unsigned short shm_nattch; /* no. of current attaches */

unsigned short shm_unused; /* compatibility */

void *shm_unused2; /* ditto – used by DIPC */

void *shm_unused3; /* unused */|

};

两个进程在使用此共享内存空间之前,需要在进程地址空间与共享内存空间之间建立联系,即将共享内存空间挂载到进程中。

系统对共享内存做了以下限制:

#define SHMMAX 0x/* max shared seg size (bytes) 更大共享段大小 */

#define SHMMIN 1 /* min shared seg size (bytes) 最小共享段大小 */

#define SHMMNI 4096 /* max num of segs system wide */

#define SHMALL (SHMMAX/getpagesize()*(SHMMNI/16))|

define SHMSEG SHMMNI /* max shared segs per process */

Linux 共享内存管理

1.创建共享内存

#include #include

/*

* 之一个参数为 key 值,一般由 ftok() 函数产生

* 第二个参数为欲创建的共享内存段大小(单位为字节)

* 第三个参数用来标识共享内存段的创建标识

*/

int shmget(key_t key, size_t size, int shm);

2.共享内存控制

#include #include

/*

* 之一个参数为要操作的共享内存标识符

* 第二个参数为要执行的操作

* 第三个参数为 shmid_ds 结构的临时共享内存变量信息

*/

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

3.映射共享内存对象

系统调用 shmat() 函数实现将一个共享内存段映射到调闷宴用进程的数据段中,并返回内存空间首地址,其函数声明如下:

#include

#include

/*

* 之一个参数为要操作的共享内存标识符

* 第二个参数用来指定共享内存的映射地址,非0则为此参数,为0的话由系统分配

* 第三个参数用来指定共享内存段的访问权限和映射条件

*/

void *shmat(int shmid, const void *shmaddr, int shm);

4.分离共享内存对象

在使用完毕共享内存空间后,需要使用 shmdt() 函数调用将其与当前进程分离。函数声明如下:嫌罩仔

#include

#include

/*

* 参数为分配的共享内存首地址

*/

int shmdt(const void *shmaddr);

共享内存在父子进程间遵循的约定

1.使用 fork() 函数创建一个子进程后,该进程继承父亲进程挂载的共享内存。

2.如果调用 exec() 执行一个新的程序,则所有挂载的共享内存将被自动卸载。

3.如果在某个进程中调用了 exit() 函数,所有挂载的共享内存将与当前进程脱离关系。

程序实例

申请一段共享内存,父进程在首地址处存入一整数,子进程读出。

#include

#include

#include

#include

#include

#include

#define SHM_SIZE 1024

int main()

{

int shm_id, pid;

int *ptr = NULL;

/* 申请共享内存 */

shm_id = shmget((key_t)1004, SHM_SIZE, IPC_CREAT | 0600);

/* 映射共享内存到进程地址空间 */

ptr = (int*)shmat(shm_id, 0, 0);

printf(“Attach addr is %p “, ptr);

*ptr = 1004;

printf(“The Value of Parent is : %d “, *ptr);

if((pid=fork()) == -1){

perror(“fork Err”);

exit(0);

}

else if(!pid){

printf(“The Value of Child is : %d “, *ptr);

exit(0);

}else{

sleep(1);

/* 解除映射 */

shmdt(ptr);

/* 删除共享内存 */

shmctl(shm_id, IPC_RMID, 0);

}

return 0;

}

输出结果:

640

linux 给进程传递数据的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于linux 给进程传递数据,运用 Linux 实现进程间传递数据技巧,在linux编程中若一个用户程序希望将一组数据传递给kernel有几种方式,架构师进阶:Linux进程间如何共享内存的信息别忘了在本站进行查找喔。


数据运维技术 » 运用 Linux 实现进程间传递数据技巧 (linux 给进程传递数据)