浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路

浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路
浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路

上一篇文章Android进程间通信(IPC)机制Binder简要介绍和学习计划简要介绍了Android系统进程间通信机制Binder的总体架构,它由Client、Server、Service Manager和驱动程序Binder四个组件构成。本文着重介绍组件Service Manager,它是整个Binder机制的守护进程,用来管理开发者创建的各种Server,并且向Client提供查询Server远程接口的功能。

既然Service Manager组件是用来管理Server并且向Client提供查询Server 远程接口的功能,那么,Service Manager就必然要和Server以及Client进行通信了。我们知道,Service Manger、Client和Server三者分别是运行在独立的进程当中,这样它们之间的通信也属于进程间通信了,而且也是采用Binder机制进行进程间通信,因此,Service Manager在充当Binder机制的守护进程的角色的同时,也在充当Server的角色,然而,它是一种特殊的Server,下面我们将会看到它的特殊之处。

与Service Manager相关的源代码较多,这里不会完整去分析每一行代码,主要是带着Service Manager是如何成为整个Binder机制中的守护进程这条主线来一步一步地深入分析相关源代码,包括从用户空间到内核空间的相关源代码。希望读者在阅读下面的内容之前,先阅读一下前一篇文章提到的两个参考资料Android 深入浅出之Binder机制和Android Binder设计与实现,熟悉相关概念和数据结构,这有助于理解下面要分析的源代码。

Service Manager在用户空间的源代码位于

frameworks/base/cmds/servicemanager目录下,主要是由binder.h、binder.c和service_manager.c三个文件组成。Service Manager的入口位于

service_manager.c文件中的main函数:

view plain

1.int main(int argc, char **argv)

2.{

3.struct binder_state *bs;

4.void *svcmgr = BINDER_SERVICE_MANAGER;

5.

6. bs = binder_open(128*1024);

7.

8.if (binder_become_context_manager(bs)) {

9. LOGE("cannot become context manager (%s)\n", strerror(errno));

10.return -1;

11. }

12.

13. svcmgr_handle = svcmgr;

14. binder_loop(bs, svcmgr_handler);

15.return 0;

16.}

main函数主要有三个功能:一是打开Binder设备文件;二是告诉Binder驱动程序自己是Binder上下文管理者,即我们前面所说的守护进程;三是进入一个无穷循环,充当Server的角色,等待Client的请求。进入这三个功能之间,先来看一下这里用到的结构体binder_state、宏BINDER_SERVICE_MANAGER的定义:

struct binder_state定义在frameworks/base/cmds/servicemanager/binder.c 文件中:

view plain

1.struct binder_state

2.{

3.int fd;

4.void *mapped;

5. unsigned mapsize;

6.};

fd是文件描述符,即表示打开的/dev/binder设备文件描述符;mapped是把设备文件/dev/binder映射到进程空间的起始地址;mapsize是上述内存映射空间的大小。

宏BINDER_SERVICE_MANAGER定义

frameworks/base/cmds/servicemanager/binder.h文件中:

view plain

1./* the one magic object */

2.#define BINDER_SERVICE_MANAGER ((void*) 0)

它表示Service Manager的句柄为0。Binder通信机制使用句柄来代表远程接口,这个句柄的意义和Windows编程中用到的句柄是差不多的概念。前面说到,Service Manager在充当守护进程的同时,它充当Server的角色,当它作为远程接口使用时,它的句柄值便为0,这就是它的特殊之处,其余的Server的远程接口句柄值都是一个大于0 而且由Binder驱动程序自动进行分配的。

函数首先是执行打开Binder设备文件的操作binder_open,这个函数位于frameworks/base/cmds/servicemanager/binder.c文件中:

view plain

1.struct binder_state *binder_open(unsigned mapsize)

2.{

3.struct binder_state *bs;

4.

5. bs = malloc(sizeof(*bs));

6.if (!bs) {

7. errno = ENOMEM;

8.return 0;

9. }

10.

11. bs->fd = open("/dev/binder", O_RDWR);

12.if (bs->fd < 0) {

13. fprintf(stderr,"binder: cannot open device (%s)\n",

14. strerror(errno));

15.goto fail_open;

16. }

17.

18. bs->mapsize = mapsize;

19. bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

20.if (bs->mapped == MAP_FAILED) {

21. fprintf(stderr,"binder: cannot map device (%s)\n",

22. strerror(errno));

23.goto fail_map;

24. }

25.

26./* TODO: check version */

27.

28.return bs;

29.

30.fail_map:

31. close(bs->fd);

32.fail_open:

33. free(bs);

34.return 0;

35.}

通过文件操作函数open来打开/dev/binder设备文件。设备文件/dev/binder 是在Binder驱动程序模块初始化的时候创建的,我们先看一下这个设备文件的创建

过程。进入到kernel/common/drivers/staging/android目录中,打开binder.c文件,可以看到模块初始化入口binder_init:

view plain

1.static struct file_operations binder_fops = {

2. .owner = THIS_MODULE,

3. .poll = binder_poll,

4. .unlocked_ioctl = binder_ioctl,

5. .mmap = binder_mmap,

6. .open = binder_open,

7. .flush = binder_flush,

8. .release = binder_release,

9.};

10.

11.static struct miscdevice binder_miscdev = {

12. .minor = MISC_DYNAMIC_MINOR,

13. .name = "binder",

14. .fops = &binder_fops

15.};

16.

17.static int __init binder_init(void)

18.{

19.int ret;

20.

21. binder_proc_dir_entry_root = proc_mkdir("binder", NULL);

22.if (binder_proc_dir_entry_root)

23. binder_proc_dir_entry_proc = proc_mkdir("proc", binder_proc_dir_entr

y_root);

24. ret = misc_register(&binder_miscdev);

25.if (binder_proc_dir_entry_root) {

26. create_proc_read_entry("state", S_IRUGO, binder_proc_dir_entry_root,

binder_read_proc_state, NULL);

27. create_proc_read_entry("stats", S_IRUGO, binder_proc_dir_entry_root,

binder_read_proc_stats, NULL);

28. create_proc_read_entry("transactions", S_IRUGO, binder_proc_dir_entr

y_root, binder_read_proc_transactions, NULL);

29. create_proc_read_entry("transaction_log", S_IRUGO, binder_proc_dir_e

ntry_root, binder_read_proc_transaction_log, &binder_transaction_log);

30. create_proc_read_entry("failed_transaction_log", S_IRUGO, binder_pro

c_dir_entry_root, binder_read_proc_transaction_log, &binder_transaction_log_ failed);

31. }

32.return ret;

33.}

34.

35.device_initcall(binder_init);

创建设备文件的地方在misc_register函数里面,关于misc设备的注册,我们在Android日志系统驱动程序Logger源代码分析一文中有提到,有兴趣的读取不访去了解一下。其余的逻辑主要是在/proc目录创建各种Binder相关的文件,供用户访问。从设备文件的操作方法binder_fops可以看出,前面的binder_open函数执行语句:

view plain

1.bs->fd = open("/dev/binder", O_RDWR);

就进入到Binder驱动程序的binder_open函数了:

view plain

1.static int binder_open(struct inode *nodp, struct file *filp)

2.{

3.struct binder_proc *proc;

4.

5.if (binder_debug_mask & BINDER_DEBUG_OPEN_CLOSE)

6. printk(KERN_INFO "binder_open: %d:%d\n", current->group_leader->pid,

current->pid);

7.

8. proc = kzalloc(sizeof(*proc), GFP_KERNEL);

9.if (proc == NULL)

10.return -ENOMEM;

11. get_task_struct(current);

12. proc->tsk = current;

13. INIT_LIST_HEAD(&proc->todo);

14. init_waitqueue_head(&proc->wait);

15. proc->default_priority = task_nice(current);

16. mutex_lock(&binder_lock);

17. binder_stats.obj_created[BINDER_STAT_PROC]++;

18. hlist_add_head(&proc->proc_node, &binder_procs);

19. proc->pid = current->group_leader->pid;

20. INIT_LIST_HEAD(&proc->delivered_death);

21. filp->private_data = proc;

22. mutex_unlock(&binder_lock);

23.

24.if (binder_proc_dir_entry_proc) {

25.char strbuf[11];

26. snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);

27. remove_proc_entry(strbuf, binder_proc_dir_entry_proc);

28. create_proc_read_entry(strbuf, S_IRUGO, binder_proc_dir_entry_proc,

binder_read_proc_proc, proc);

29. }

30.

31.return 0;

32.}

这个函数的主要作用是创建一个struct binder_proc数据结构来保存打开设备文件/dev/binder的进程的上下文信息,并且将这个进程上下文信息保存在打开文件结构struct file的私有数据成员变量private_data中,这样,在执行其它文件操作时,就通过打开文件结构struct file来取回这个进程上下文信息了。这个进程上下文信息同时还会保存在一个全局哈希表binder_procs中,驱动程序内部使用。binder_procs定义在文件的开头:

view plain

1.static HLIST_HEAD(binder_procs);

结构体struct binder_proc也是定义在

kernel/common/drivers/staging/android/binder.c文件中:

view plain

1.struct binder_proc {

2.struct hlist_node proc_node;

3.struct rb_root threads;

4.struct rb_root nodes;

5.struct rb_root refs_by_desc;

6.struct rb_root refs_by_node;

7.int pid;

8.struct vm_area_struct *vma;

9.struct task_struct *tsk;

10.struct files_struct *files;

11.struct hlist_node deferred_work_node;

12.int deferred_work;

13.void *buffer;

14.ptrdiff_t user_buffer_offset;

15.

16.struct list_head buffers;

17.struct rb_root free_buffers;

18.struct rb_root allocated_buffers;

19.size_t free_async_space;

20.

21.struct page **pages;

22.size_t buffer_size;

23. uint32_t buffer_free;

24.struct list_head todo;

25. wait_queue_head_t wait;

26.struct binder_stats stats;

27.struct list_head delivered_death;

28.int max_threads;

29.int requested_threads;

30.int requested_threads_started;

31.int ready_threads;

32.long default_priority;

33.};

这个结构体的成员比较多,这里就不一一说明了,简单解释一下四个成员变量threads、nodes、refs_by_desc和refs_by_node,其它的我们在遇到的时候再详细解释。这四个成员变量都是表示红黑树的节点,也就是说,binder_proc分别挂会四个红黑树下。threads树用来保存binder_proc进程内用于处理用户请求的线程,它的最大数量由max_threads来决定;node树成用来保存binder_proc进程内的Binder实体;refs_by_desc树和refs_by_node树用来保存binder_proc进程内的Binder引用,即引用的其它进程的Binder实体,它分别用两种方式来组织红黑树,一种是以句柄作来key值来组织,一种是以引用的实体节点的地址值作来key值来组织,它们都是表示同一样东西,只不过是为了内部查找方便而用两个红黑树来表示。

这样,打开设备文件/dev/binder的操作就完成了,接着是对打开的设备文件进行内存映射操作mmap:

view plain

1.bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

对应Binder驱动程序的binder_mmap函数:

view plain

1.static int binder_mmap(struct file *filp, struct vm_area_struct *vma)

2.{

3.int ret;

4.struct vm_struct *area;

5.struct binder_proc *proc = filp->private_data;

6.const char *failure_string;

7.struct binder_buffer *buffer;

8.

9.if ((vma->vm_end - vma->vm_start) > SZ_4M)

10. vma->vm_end = vma->vm_start + SZ_4M;

11.

12.if (binder_debug_mask & BINDER_DEBUG_OPEN_CLOSE)

13. printk(KERN_INFO

14."binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",

15. proc->pid, vma->vm_start, vma->vm_end,

16. (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,

17. (unsigned long)pgprot_val(vma->vm_page_prot));

18.

19.if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {

20. ret = -EPERM;

21. failure_string = "bad vm_flags";

22.goto err_bad_arg;

23. }

24. vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

25.

26.if (proc->buffer) {

27. ret = -EBUSY;

28. failure_string = "already mapped";

29.goto err_already_mapped;

30. }

31.

32. area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);

33.if (area == NULL) {

34. ret = -ENOMEM;

35. failure_string = "get_vm_area";

36.goto err_get_vm_area_failed;

37. }

38. proc->buffer = area->addr;

39. proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;

40.

41.#ifdef CONFIG_CPU_CACHE_VIPT

42.if (cache_is_vipt_aliasing()) {

43.while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) {

44. printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p bad alignment\

n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer);

45. vma->vm_start += PAGE_SIZE;

46. }

47. }

48.#endif

49. proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_s

tart) / PAGE_SIZE), GFP_KERNEL);

50.if (proc->pages == NULL) {

51. ret = -ENOMEM;

52. failure_string = "alloc page array";

53.goto err_alloc_pages_failed;

54. }

55. proc->buffer_size = vma->vm_end - vma->vm_start;

56.

57. vma->vm_ops = &binder_vm_ops;

58. vma->vm_private_data = proc;

59.

60.if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_

SIZE, vma)) {

61. ret = -ENOMEM;

62. failure_string = "alloc small buf";

63.goto err_alloc_small_buf_failed;

64. }

65. buffer = proc->buffer;

66. INIT_LIST_HEAD(&proc->buffers);

67. list_add(&buffer->entry, &proc->buffers);

68. buffer->free = 1;

69. binder_insert_free_buffer(proc, buffer);

70. proc->free_async_space = proc->buffer_size / 2;

71. barrier();

72. proc->files = get_files_struct(current);

73. proc->vma = vma;

74.

75./*printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p\n", proc->pid, vma->

vm_start, vma->vm_end, proc->buffer);*/

76.return 0;

77.

78.err_alloc_small_buf_failed:

79. kfree(proc->pages);

80. proc->pages = NULL;

81.err_alloc_pages_failed:

82. vfree(proc->buffer);

83. proc->buffer = NULL;

84.err_get_vm_area_failed:

85.err_already_mapped:

86.err_bad_arg:

87. printk(KERN_ERR "binder_mmap: %d %lx-%lx %s failed %d\n", proc->pid, vma

->vm_start, vma->vm_end, failure_string, ret);

88.return ret;

89.}

函数首先通过filp->private_data得到在打开设备文件/dev/binder时创建的struct binder_proc结构。内存映射信息放在vma参数中,注意,这里的vma的数据类型是struct vm_area_struct,它表示的是一块连续的虚拟地址空间区域,在函数变量声明的地方,我们还看到有一个类似的结构体struct vm_struct,这个数据结构也是表示一块连续的虚拟地址空间区域,那么,这两者的区别是什么呢?在Linux 中,struct vm_area_struct表示的虚拟地址是给进程使用的,而struct vm_struct

表示的虚拟地址是给内核使用的,它们对应的物理页面都可以是不连续的。struct vm_area_struct表示的地址空间范围是0~3G,而struct vm_struct表示的地址空间范围是(3G + 896M + 8M) ~ 4G。struct vm_struct表示的地址空间范围为什么不是3G~4G呢?原来,3G ~ (3G + 896M)范围的地址是用来映射连续的物理页面的,这个范围的虚拟地址和对应的实际物理地址有着简单的对应关系,即对应0~896M 的物理地址空间,而(3G + 896M) ~ (3G + 896M + 8M)是安全保护区域(例如,所有指向这8M地址空间的指针都是非法的),因此struct vm_struct使用(3G + 896M + 8M) ~ 4G地址空间来映射非连续的物理页面。有关Linux的内存管理知识,可以参考Android学习启动篇一文提到的《Understanding the Linux Kernel》一书中的第8章。

这里为什么会同时使用进程虚拟地址空间和内核虚拟地址空间来映射同一个物理页面呢?这就是Binder进程间通信机制的精髓所在了,同一个物理页面,一方映射到进程虚拟地址空间,一方面映射到内核虚拟地址空间,这样,进程和内核之间就可以减少一次内存拷贝了,提到了进程间通信效率。举个例子如,Client要将一块内存数据传递给Server,一般的做法是,Client将这块数据从它的进程空间拷贝到内核空间中,然后内核再将这个数据从内核空间拷贝到Server的进程空间,这样,Server就可以访问这个数据了。但是在这种方法中,执行了两次内存拷贝操作,而采用我们上面提到的方法,只需要把Client进程空间的数据拷贝一次到内核空间,然后Server与内核共享这个数据就可以了,整个过程只需要执行一次内存拷贝,提高了效率。

binder_mmap的原理讲完了,这个函数的逻辑就好理解了。不过,这里还是先要解释一下struct binder_proc结构体的几个成员变量。buffer成员变量是一个void*指针,它表示要映射的物理内存在内核空间中的起始位置;buffer_size成员变量是一个size_t类型的变量,表示要映射的内存的大小;pages成员变量是一个struct page*类型的数组,struct page是用来描述物理页面的数据结构;

user_buffer_offset成员变量是一个ptrdiff_t类型的变量,它表示的是内核使用的虚拟地址与进程使用的虚拟地址之间的差值,即如果某个物理页面在内核空间中对应

的虚拟地址是addr的话,那么这个物理页面在进程空间对应的虚拟地址就为addr + user_buffer_offset。

再解释一下Binder驱动程序管理这个内存映射地址空间的方法,即是如何管理buffer ~ (buffer + buffer_size)这段地址空间的,这个地址空间被划分为一段一段来管理,每一段是结构体struct binder_buffer来描述:

view plain

1.struct binder_buffer {

2.struct list_head entry; /* free and allocated entries by addesss */

3.struct rb_node rb_node; /* free entry by size or allocated entry */

4./* by address */

5. unsigned free : 1;

6. unsigned allow_user_free : 1;

7. unsigned async_transaction : 1;

8. unsigned debug_id : 29;

9.

10.struct binder_transaction *transaction;

11.

12.struct binder_node *target_node;

13.size_t data_size;

14.size_t offsets_size;

15. uint8_t data[0];

16.};

每一个binder_buffer通过其成员entry按从低址到高地址连入到struct binder_proc中的buffers表示的链表中去,同时,每一个binder_buffer又分为正在使用的和空闲的,通过free成员变量来区分,空闲的binder_buffer通过成员变量

rb_node连入到struct binder_proc中的free_buffers表示的红黑树中去,正在使用的binder_buffer通过成员变量rb_node连入到struct binder_proc中的

allocated_buffers表示的红黑树中去。这样做当然是为了方便查询和维护这块地址

空间了,这一点我们可以从其它的代码中看到,等遇到的时候我们再分析。

终于可以回到binder_mmap这个函数来了,首先是对参数作一些健康体检(sanity check),例如,要映射的内存大小不能超过SIZE_4M,即4M,回到service_manager.c中的main 函数,这里传进来的值是128 * 1024个字节,即128K,这个检查没有问题。通过健康体检后,调用get_vm_area函数获得一个空闲的

vm_struct区间,并初始化proc结构体的buffer、user_buffer_offset、pages和buffer_size和成员变量,接着调用binder_update_page_range来为虚拟地址空间

proc->buffer ~ proc->buffer + PAGE_SIZE分配一个空闲的物理页面,同时这段地

址空间使用一个binder_buffer来描述,分别插入到proc->buffers链表和

proc->free_buffers红黑树中去,最后,还初始化了proc结构体的free_async_space、files和vma三个成员变量。

这里,我们继续进入到binder_update_page_range函数中去看一下Binder

驱动程序是如何实现把一个物理页面同时映射到内核空间和进程空间去的:

view plain

1.static int binder_update_page_range(struct binder_proc *proc, int allocate,

2.void *start, void *end, struct vm_area_struct *vma)

3.{

4.void *page_addr;

5. unsigned long user_page_addr;

6.struct vm_struct tmp_area;

7.struct page **page;

8.struct mm_struct *mm;

9.

10.if (binder_debug_mask & BINDER_DEBUG_BUFFER_ALLOC)

11. printk(KERN_INFO "binder: %d: %s pages %p-%p\n",

12. proc->pid, allocate ? "allocate" : "free", start, end);

13.

14.if (end <= start)

15.return 0;

16.

17.if (vma)

18. mm = NULL;

19.else

20. mm = get_task_mm(proc->tsk);

21.

22.if (mm) {

23. down_write(&mm->mmap_sem);

24. vma = proc->vma;

25. }

26.

27.if (allocate == 0)

28.goto free_range;

29.

30.if (vma == NULL) {

31. printk(KERN_ERR "binder: %d: binder_alloc_buf failed to "

32."map pages in userspace, no vma\n", proc->pid);

33.goto err_no_vma;

34. }

35.

36.for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {

37.int ret;

38.struct page **page_array_ptr;

39. page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];

40.

41. BUG_ON(*page);

42. *page = alloc_page(GFP_KERNEL | __GFP_ZERO);

43.if (*page == NULL) {

44. printk(KERN_ERR "binder: %d: binder_alloc_buf failed "

45."for page at %p\n", proc->pid, page_addr);

46.goto err_alloc_page_failed;

47. }

48. tmp_area.addr = page_addr;

49. tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;

50. page_array_ptr = page;

51. ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);

52.if (ret) {

53. printk(KERN_ERR "binder: %d: binder_alloc_buf failed "

54."to map page at %p in kernel\n",

55. proc->pid, page_addr);

56.goto err_map_kernel_failed;

57. }

58. user_page_addr =

59. (uintptr_t)page_addr + proc->user_buffer_offset;

60. ret = vm_insert_page(vma, user_page_addr, page[0]);

61.if (ret) {

62. printk(KERN_ERR "binder: %d: binder_alloc_buf failed "

63."to map page at %lx in userspace\n",

64. proc->pid, user_page_addr);

65.goto err_vm_insert_page_failed;

66. }

67./* vm_insert_page does not seem to increment the refcount */

68. }

69.if (mm) {

70. up_write(&mm->mmap_sem);

71. mmput(mm);

72. }

73.return 0;

74.

75.free_range:

76.for (page_addr = end - PAGE_SIZE; page_addr >= start;

77. page_addr -= PAGE_SIZE) {

78. page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];

79.if (vma)

80. zap_page_range(vma, (uintptr_t)page_addr +

81. proc->user_buffer_offset, PAGE_SIZE, NULL);

82.err_vm_insert_page_failed:

83. unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);

84.err_map_kernel_failed:

85. __free_page(*page);

86. *page = NULL;

87.err_alloc_page_failed:

88. ;

89. }

90.err_no_vma:

91.if (mm) {

92. up_write(&mm->mmap_sem);

93. mmput(mm);

94. }

95.return -ENOMEM;

96.}

这个函数既可以分配物理页面,也可以用来释放物理页面,通过allocate参数来区别,这里我们只关注分配物理页面的情况。要分配物理页面的虚拟地址空间范围为(start ~ end),函数前面的一些检查逻辑就不看了,直接看中间的for循环:view plain

1.for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {

2.int ret;

3.struct page **page_array_ptr;

4. page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];

5.

6. BUG_ON(*page);

7. *page = alloc_page(GFP_KERNEL | __GFP_ZERO);

8.if (*page == NULL) {

9. printk(KERN_ERR "binder: %d: binder_alloc_buf failed "

10."for page at %p\n", proc->pid, page_addr);

11.goto err_alloc_page_failed;

12. }

13. tmp_area.addr = page_addr;

14. tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;

15. page_array_ptr = page;

16. ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);

17.if (ret) {

18. printk(KERN_ERR "binder: %d: binder_alloc_buf failed "

19."to map page at %p in kernel\n",

20. proc->pid, page_addr);

21.goto err_map_kernel_failed;

22. }

23. user_page_addr =

24. (uintptr_t)page_addr + proc->user_buffer_offset;

25. ret = vm_insert_page(vma, user_page_addr, page[0]);

26.if (ret) {

27. printk(KERN_ERR "binder: %d: binder_alloc_buf failed "

28."to map page at %lx in userspace\n",

29. proc->pid, user_page_addr);

30.goto err_vm_insert_page_failed;

31. }

32./* vm_insert_page does not seem to increment the refcount */

33.}

首先是调用alloc_page来分配一个物理页面,这个函数返回一个struct page 物理页面描述符,根据这个描述的内容初始化好struct vm_struct tmp_area结构体,然后通过map_vm_area将这个物理页面插入到tmp_area描述的内核空间去,接着通过page_addr + proc->user_buffer_offset获得进程虚拟空间地址,并通过

vm_insert_page函数将这个物理页面插入到进程地址空间去,参数vma代表了要插入的进程的地址空间。

这样,frameworks/base/cmds/servicemanager/binder.c文件中的

binder_open函数就描述完了,回到

frameworks/base/cmds/servicemanager/service_manager.c文件中的main函数,下一步就是调用binder_become_context_manager来通知Binder驱动程序自己是Binder机制的上下文管理者,即守护进程。binder_become_context_manager函数位于frameworks/base/cmds/servicemanager/binder.c文件中:

view plain

1.int binder_become_context_manager(struct binder_state *bs)

2.{

3.return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);

4.}

这里通过调用ioctl文件操作函数来通知Binder驱动程序自己是守护进程,命令号是BINDER_SET_CONTEXT_MGR,没有参数。

BINDER_SET_CONTEXT_MGR定义为:

view plain

1.#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)

这样就进入到Binder驱动程序的binder_ioctl函数,我们只关注

BINDER_SET_CONTEXT_MGR命令:

view plain

1.static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long

arg)

2.{

3.int ret;

4.struct binder_proc *proc = filp->private_data;

5.struct binder_thread *thread;

6. unsigned int size = _IOC_SIZE(cmd);

7.void __user *ubuf = (void __user *)arg;

8.

9./*printk(KERN_INFO "binder_ioctl: %d:%d %x %lx\n", proc->pid, current->p

id, cmd, arg);*/

10.

11. ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_us

er_error < 2);

12.if (ret)

13.return ret;

14.

15. mutex_lock(&binder_lock);

16.thread = binder_get_thread(proc);

17.if (thread == NULL) {

18. ret = -ENOMEM;

19.goto err;

20. }

21.

22.switch (cmd) {

23. ......

24.case BINDER_SET_CONTEXT_MGR:

25.if (binder_context_mgr_node != NULL) {

26. printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");

27. ret = -EBUSY;

28.goto err;

29. }

30.if (binder_context_mgr_uid != -1) {

31.if (binder_context_mgr_uid != current->cred->euid) {

32. printk(KERN_ERR "binder: BINDER_SET_"

33."CONTEXT_MGR bad uid %d != %d\n",

34. current->cred->euid,

35. binder_context_mgr_uid);

36. ret = -EPERM;

37.goto err;

38. }

39. } else

40. binder_context_mgr_uid = current->cred->euid;

41. binder_context_mgr_node = binder_new_node(proc, NULL, NULL);

42.if (binder_context_mgr_node == NULL) {

43. ret = -ENOMEM;

44.goto err;

45. }

46. binder_context_mgr_node->local_weak_refs++;

47. binder_context_mgr_node->local_strong_refs++;

48. binder_context_mgr_node->has_strong_ref = 1;

49. binder_context_mgr_node->has_weak_ref = 1;

50.break;

51. ......

52.default:

53. ret = -EINVAL;

54.goto err;

55. }

56. ret = 0;

57.err:

58.if (thread)

59.thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;

60. mutex_unlock(&binder_lock);

61. wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_err

or < 2);

62.if (ret && ret != -ERESTARTSYS)

63. printk(KERN_INFO "binder: %d:%d ioctl %x %lx returned %d\n", proc->p

id, current->pid, cmd, arg, ret);

64.return ret;

65.}

继续分析这个函数之前,又要解释两个数据结构了,一个是struct

binder_thread结构体,顾名思久,它表示一个线程,这里就是执行

binder_become_context_manager函数的线程了。

view plain

1.struct binder_thread {

2.struct binder_proc *proc;

3.struct rb_node rb_node;

4.int pid;

5.int looper;

6.struct binder_transaction *transaction_stack;

7.struct list_head todo;

8. uint32_t return_error; /* Write failed, return error code in read buf */

9. uint32_t return_error2; /* Write failed, return error code in read */

10./* buffer. Used when sending a reply to a dead process that */

11./* we are also waiting on */

12. wait_queue_head_t wait;

13.struct binder_stats stats;

14.};

proc表示这个线程所属的进程。struct binder_proc有一个成员变量threads,它的类型是rb_root,它表示一查红黑树,把属于这个进程的所有线程都组织起来,struct binder_thread的成员变量rb_node就是用来链入这棵红黑树的节点了。looper成员变量表示线程的状态,它可以取下面这几个值:

view plain

1.enum {

2. BINDER_LOOPER_STATE_REGISTERED = 0x01,

3. BINDER_LOOPER_STATE_ENTERED = 0x02,

4. BINDER_LOOPER_STATE_EXITED = 0x04,

5. BINDER_LOOPER_STATE_INVALID = 0x08,

6. BINDER_LOOPER_STATE_WAITING = 0x10,

7. BINDER_LOOPER_STATE_NEED_RETURN = 0x20

8.};

其余的成员变量,transaction_stack表示线程正在处理的事务,todo表示发往该线程的数据列表,return_error和return_error2表示操作结果返回码,wait用来阻塞线程等待某个事件的发生,stats用来保存一些统计信息。这些成员变量遇到的时候再分析它们的作用。

另外一个数据结构是struct binder_node,它表示一个binder实体:

view plain

1.struct binder_node {

2.int debug_id;

3.struct binder_work work;

4.union {

5.struct rb_node rb_node;

6.struct hlist_node dead_node;

7. };

8.struct binder_proc *proc;

9.struct hlist_head refs;

10.int internal_strong_refs;

11.int local_weak_refs;

12.int local_strong_refs;

13.void __user *ptr;

14.void __user *cookie;

15. unsigned has_strong_ref : 1;

16. unsigned pending_strong_ref : 1;

17. unsigned has_weak_ref : 1;

18. unsigned pending_weak_ref : 1;

19. unsigned has_async_transaction : 1;

20. unsigned accept_fds : 1;

21.int min_priority : 8;

22.struct list_head async_todo;

23.};

rb_node和dead_node组成一个联合体。如果这个Binder实体还在正常使用,则使用rb_node来连入proc->nodes所表示的红黑树的节点,这棵红黑树用来组织属于这个进程的所有Binder实体;如果这个Binder实体所属的进程已经销毁,而这个Binder实体又被其它进程所引用,则这个Binder实体通过dead_node进入到一个哈希表中去存放。proc成员变量就是表示这个Binder实例所属于进程了。refs 成员变量把所有引用了该Binder实体的Binder引用连接起来构成一个链表。internal_strong_refs、local_weak_refs和local_strong_refs表示这个Binder实体的引用计数。ptr和cookie成员变量分别表示这个Binder实体在用户空间的地址以及附加数据。其余的成员变量就不描述了,遇到的时候再分析。

现在回到binder_ioctl函数中,首先是通过filp->private_data获得proc变量,这里binder_mmap函数是一样的。接着通过binder_get_thread函数获得线程信息,我们来看一下这个函数:

view plain

1.static struct binder_thread *binder_get_thread(struct binder_proc *proc)

2.{

3.struct binder_thread *thread = NULL;

4.struct rb_node *parent = NULL;

5.struct rb_node **p = &proc->threads.rb_node;

6.

7.while (*p) {

8. parent = *p;

9.thread = rb_entry(parent, struct binder_thread, rb_node);

10.

11.if (current->pid < thread->pid)

12. p = &(*p)->rb_left;

13.else if (current->pid > thread->pid)

14. p = &(*p)->rb_right;

15.else

16.break;

17. }

18.if (*p == NULL) {

19.thread = kzalloc(sizeof(*thread), GFP_KERNEL);

20.if (thread == NULL)

21.return NULL;

22. binder_stats.obj_created[BINDER_STAT_THREAD]++;

23.thread->proc = proc;

24.thread->pid = current->pid;

25. init_waitqueue_head(&thread->wait);

26. INIT_LIST_HEAD(&thread->todo);

27. rb_link_node(&thread->rb_node, parent, p);

28. rb_insert_color(&thread->rb_node, &proc->threads);

29.thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;

30.thread->return_error = BR_OK;

31.thread->return_error2 = BR_OK;

32. }

33.return thread;

34.}

这里把当前线程current的pid作为键值,在进程proc->threads表示的红黑树中进行查找,看是否已经为当前线程创建过了binder_thread信息。在这个场景下,由于当前线程是第一次进到这里,所以肯定找不到,即*p == NULL成立,于是,就为当前线程创建一个线程上下文信息结构体binder_thread,并初始化相应成员变量,并插入到proc->threads所表示的红黑树中去,下次要使用时就可以从proc中找到了。注意,这里的thread->looper

= BINDER_LOOPER_STATE_NEED_RETURN。

回到binder_ioctl函数,继续往下面,有两个全局变量

binder_context_mgr_node和binder_context_mgr_uid,它定义如下:

view plain

1.static struct binder_node *binder_context_mgr_node;

2.static uid_t binder_context_mgr_uid = -1;

binder_context_mgr_node用来表示Service Manager实体,

binder_context_mgr_uid表示Service Manager守护进程的uid。在这个场景下,由于当前线程是第一次进到这里,所以binder_context_mgr_node为NULL,

进程间通信(1)

无名管道的用法 无名管道又被称为pipe,是进程间通信的一种方式。pipe具有以下特点: ●只能用于具有血缘关系的进程之间 ●半双工的通信模式,具有固定的读端和写端 ●一种特殊的文件,存在于内存中。可通过read、write对其操作 因为pipe存在于内存中,所以无法像操作普通文件那样通过指定路径来打开文件。通常的做法是在父进程中创建管道,再创建子进程。由于子进程继承了父进程打开的文件描述符,所以父子进程就可以通过创建的管道进行通信。 为了在父进程中创建管道,需要先定义一个包含两个元素的整型数组,用来存放管道读端和写端对应的文件描述符。该数组在创建管道时作为参数传递。要注意的是,管道是一种半双工方式,即对于进程来说,要么只能读管道,要么只能写管道。不允许对管道又读又写。 其中数组的第一个元素固定代表管道的读端,第二个元素代表管道的写端。对于一个进程来说,只会用到其中一个。 若读取管道时没有数据,则进程会被阻塞,直到有数据可读。写管道时除非管道已满,一般情况下写入操作很快会返回。 这里假设父进程读管道,子进程写管道。参考代码如下所示: int pfd[2]; pid_t pid; if ( pipe(pfd) < 0 ) // 创建管道失败 { perror(“fail to create pipe : ”); exit(-1); } if ( (pid=fork()) == -1 ) // 创建子进程失败 { perror(“fail to create child process : ”); exit(-1); } else if ( pid == 0 ) // 子进程

进程和进程间的通信

1、进程概念; 2、进程的控制: (1)生成一个进程:fork (2)进程的同步:wait waitpid (3)进程的退出:exit _exit (4)进程“脱胎换骨”:exec函数族 3、进程通信 (1)进程为什么需要通信? (2)linux下进程如何通信 ●早期的unix通信方式 无名管道;有名管道;信号 ●sysem v的通信方式:共享内存、消息队列、信号量 ●BSD的通信方式:socket 4、无名管道:适用于有血缘关系进程通信 小任务1:父进程通过无名管道向子进程发送字符串“Hello,you man!”,子进程接收到后显示出来,然后子进程退出,最后父进程退出。 (1)创建子进程:fork (2)创建管道 #include int pipe(int pipefd[2]); 参数说明(当管道创建成功后): pipefd[0]:读端的文件描述符; pipefd[1]:写端的文件描述 返回值:0表示创建成功,-1表示创建失败 (3)父亲写管道 write (4)儿子读管道 read (5)父亲等待儿子退出 wait 参考代码: #include #include #include #include #include int main() { int pid; int pipefd[2]; int ret;

char buf[]="Hello,young man!"; ret=pipe(pipefd);//创建管道(1) if(ret<0) { perror("Failed to create pipe:"); return -1; } pid=fork(); //能够把(1)语句放此注释的下一样?? if(pid<0) { perror("Failed to create child process:"); return -1; } if(pid>0) { close(pipefd[0]);//父进程中关闭无关的读端 write(pipefd[1],buf,strlen(buf)); wait(NULL); printf("Parent process exit!\n"); } else { char receive_buf[100]; int count; close(pipefd[1]);//子进程中关闭无关的写端 count=read(pipefd[0],receive_buf,100); if(count>0) { receive_buf[count]='\0'; printf("Child process receive a string:%s\n",receive_buf); } printf("Child process exit!\n"); } return 0; } 5、有名管道(fifo) (1)文件系统中可见,可以通过mkfifo 命令来创建一个有名管道 eg: mkfifo -m 0666 myfifo (2)有名管道的使用跟普通文件一样:open read write close,不用使用lseek!!!! 任务2: 进程1通过有名管道把键盘输入字符串发送给进程2,进程2收到后显

Windows进程间各种通信方式浅谈

Windows进程间各种通信方式浅谈 1、Windows进程间通信的各种方法 进程是装入内存并准备执行的程序,每个进程都有私有的虚拟地址空间,由代码、数据以及它可利用的系统资源(如文件、管道等)组成。 多进程/多线程是Windows操作系统的一个基本特征。Microsoft Win32应用编程接口(Application Programming Interface, API) 提供了大量支持应用程序间数据共享和交换的机制,这些机制行使的活动称为进程间通信(InterProcess Communication, IPC),进程通信就是指不同进程间进行数据共享和数据交换。 正因为使用Win32 API进行进程通信方式有多种,如何选择恰当的通信方式就成为应用开发中的一个重要问题, 下面本文将对Win32中进程通信的几种方法加以分析和比较。 2、进程通信方法 2.1 文件映射 文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作,只需简单的指针操作就可读取和修改文件的内容。 Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。 应用程序有三种方法来使多个进程共享一个文件映射对象。 (1)继承:第一个进程建立文件映射对象,它的子进程继承该对象的句柄。 (2)命名文件映射:第一个进程在建立文件映射对象时可以给该对象指定一个名字(可与文件名不同)。第二个进程可通过这个名字打开此文件映射对象。另外,第一个进程也可以通过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。 (3)句柄复制:第一个进程建立文件映射对象,然后通过其它IPC机制(有名管道、

进程同步与通信作业习题与答案

第三章 一.选择题(50题) 1.以下_B__操作系统中的技术是用来解决进程同步的。 A.管道 B.管程 C.通道 2.以下_B__不是操作系统的进程通信手段。 A.管道 B.原语 C.套接字 D.文件映射 3.如果有3个进程共享同一程序段,而且每次最多允许两个进程进入该程序段,则信号量的初值应设置为_B__。 4.设有4个进程共享一个资源,如果每次只允许一个进程使用该资源,则用P、V操作管理时信号量S的可能取值是_C__。 ,2,1,0,-1 ,1,0,-1,-2 C. 1,0,-1,-2,-3 ,3,2,1,0 5.下面有关进程的描述,是正确的__A__。 A.进程执行的相对速度不能由进程自己来控制 B.进程利用信号量的P、V 操作可以交换大量的信息 C.并发进程在访问共享资源时,不可能出现与时间有关的错误 、V操作不是原语操作 6.信号灯可以用来实现进程之间的_B__。 A.调度 B.同步与互斥 C.同步 D.互斥 7.对于两个并发进程都想进入临界区,设互斥信号量为S,若某时S=0,表示_B__。 A.没有进程进入临界区 B.有1个进程进入了临界区 C. 有2个进程进入了临界区 D. 有1个进程进入了临界区并且另一个进程正等待进入 8. 信箱通信是一种_B__方式 A.直接通信 B.间接通信 C.低级通信 D.信号量 9.以下关于临界区的说法,是正确的_C__。

A.对于临界区,最重要的是判断哪个进程先进入 B.若进程A已进入临界区,而进程B的优先级高于进程A,则进程B可以 打断进程A而自己进入临界区 C. 信号量的初值非负,在其上只能做PV操作 D.两个互斥进程在临界区内,对共享变量的操作是相同的 10. 并发是指_C__。 A.可平行执行的进程 B.可先后执行的进程 C.可同时执行的进程 D.不可中断的进程 11. 临界区是_C__。 A.一个缓冲区 B.一段数据区 C.一段程序 D.栈 12.进程在处理机上执行,它们的关系是_C__。 A.进程之间无关,系统是封闭的 B.进程之间相互依赖相互制约 C.进程之间可能有关,也可能无关 D.以上都不对 13. 在消息缓冲通信中,消息队列是一种__A__资源。 A.临界 B.共享 C.永久 D.可剥夺 14. 以下关于P、V操作的描述正确的是__D_。 A.机器指令 B. 系统调用 C.高级通信原语 D.低级通信原语 15.当对信号量进行V源语操作之后,_C__。 A.当S<0,进程继续执行 B.当S>0,要唤醒一个就绪进程 C. 当S<= 0,要唤醒一个阻塞进程 D. 当S<=0,要唤醒一个就绪 16.对临界区的正确论述是__D_。 A.临界区是指进程中用于实现进程互斥的那段代码 B. 临界区是指进程中用于实现进程同步的那段代码 C. 临界区是指进程中用于实现进程通信的那段代码 D. 临界区是指进程中访问临界资源的那段代码 17. __A__不是进程之间的通信方式。 A.过程调用 B.消息传递 C.共享存储器 D.信箱通信 18. 同步是指进程之间逻辑上的__A__关系。

进程间通信方式比较

进程间的通信方式: 1.管道(pipe)及有名管道(named pipe): 管道可用于具有亲缘关系进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。 2.信号(signal): 信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致得。 3.消息队列(message queue): 消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。 消息缓冲通信技术是由Hansen首先提出的,其基本思想是:根据”生产者-消费者”原理,利用内存中公用消息缓冲区实现进程之间的信息交换. 内存中开辟了若干消息缓冲区,用以存放消息.每当一个进程向另一个进程发送消息时,便申请一个消息缓冲区,并把已准备好的消息送到缓冲区,然后把该消息缓冲区插入到接收进程的消息队列中,最后通知接收进程.接收进程收到发送里程发来的通知后,从本进程的消息队列中摘下一消息缓冲区,取出所需的信息,然后把消息缓冲区不定期给系统.系统负责管理公用消息缓冲区以及消息的传递. 一个进程可以给若干个进程发送消息,反之,一个进程可以接收不同进程发来的消息.显然,进程中关于消息队列的操作是临界区.当发送进程正往接收进程的消息队列中添加一条消息时,接收进程不能同时从该消息队列中到出消息:反之也一样. 消息缓冲区通信机制包含以下列内容:

(1) 消息缓冲区,这是一个由以下几项组成的数据结构: 1、消息长度 2、消息正文 3、发送者 4、消息队列指针 (2)消息队列首指针m-q,一般保存在PCB中。 (1)互斥信号量m-mutex,初值为1,用于互斥访问消息队列,在PCB中设置。 (2)同步信号量m-syn,初值为0,用于消息计数,在PCB中设置。(3)发送消息原语send (4)接收消息原语receive(a) 4.共享内存(shared memory): 可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。 这种通信模式需要解决两个问题:第一个问题是怎样提供共享内存;第二个是公共内存的互斥关系则是程序开发人员的责任。 5.信号量(semaphore): 主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。 6.套接字(socket); 这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。 https://www.360docs.net/doc/719108030.html,/eroswang/archive/2007/09/04/1772350.aspx linux下的进程间通信-详解

进程间通信的四种方式

一、剪贴板 1、基础知识 剪贴板实际上是系统维护管理的一块内存区域,当在一个进程中复制数据时,是将这个数据放到该块内存区域中,当在另一个进程中粘贴数据时,是从该内存区域中取出数据。 2、函数说明: (1)、BOOL OpenClipboard( ) CWnd类的OpenClipboard函数用于打开剪贴板。若打开剪贴板成功,则返回非0值。若其他程序或当前窗口已经打开了剪贴板,则该函数返回0值,表示打开失败。若某个程序已经打开了剪贴板,则其他应用程序将不能修改剪贴板,直到前者调用了CloseClipboard函数。 (2)、BOOL EmptyClipboard(void) EmptyClipboard函数将清空剪贴板,并释放剪贴板中数据的句柄,然后将剪贴板的所有权分配给当前打开剪贴板的窗口。 (3)、HANDLE SetClipboardData(UINT uFormat, HANDLE hMem) SetClipboardData函数是以指定的剪贴板格式向剪贴板上放置数据。uFormat指定剪贴板格式,这个格式可以是已注册的格式,或是任一种标准的剪贴板格式。CF_TEXT表示文本格式,表示每行数据以回车换行(0x0a0x0d)终止,空字符作为数据的结尾。hMem指定具有指定格式的数据的句柄。hMem参数可以是NULL,指示采用延迟提交技术,则该程序必须处理WM_RENDERFORMA T和WM_RENDERALLFORMATS消息。应用程序在调用SetClipboardData函数之后,就拥有了hMem参数所标识的数据对象,该应用程序可以读取该数据对象,但在应用程序调用CloseClipboard函数之前,它不能释放该对象的句柄,或者锁定这个句柄。若hMem标识了一个内存对象,那么这个对象必须是利用GMEM_MOVEABLE标志调用GlobalAlloc函数为其分配内存。 注意:调用SetClipboardData函数的程序必须是剪贴板的拥有者,且在这之前已经打开了剪贴板。 延迟提交技术:当一个提供数据的进程创建了剪贴板数据之后,直到其他进程获取剪贴板数据之前,这些数据都要占据内存空间。若在剪贴板上放置的数据过大,就会浪费内存空间,降低对资源的利用率。为了避免这种浪费,就可以采用延迟提交计数,也就是由数据提供进程先提供一个指定格式的空剪贴板数据块,即把SetClipboardData函数的hMem参数设置为NULL。当需要获取数据的进程想要从剪贴板上得到数据时,操作系统会向数据提供进程发送WM_RENDERFORMA T消息,而数据提供进程可以响应这个消息,并在此消息的响应函数中,再一次调用SetClipboardData函数,将实际的数据放到剪贴板上。当再次调用SetClipboardData函数时,就不再需要调用OpenClipboard函数,也不再需要调用EmptyClipboard函数。也就是说,为了提高资源利用率,避免浪费内存空间,可以采用延迟提交技术。第一次调用SetClipboardData函数时,将其hMem参数设置为NULL,在剪贴板上以指定的剪贴板格式放置一个空剪贴板数据块。然后直到有其他进程需要数据或自身进程需要终止运行时再次调用SetClipboardData函数,这时才真正提交数据。 (4)、HGLOBAL GlobalAlloc( UINT uFlags,SIZE_T dwBytes); GlobalAlloc函数从堆上分配指定数目的字节。uFlags是一个标记,用来指定分配内存的方式,uFlags为0,则该标记就是默认的GMEM_FIXED。dwBytes指定分配的字节数。

linux进程间通讯的几种方式的特点和优缺点

1. # 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 # 有名管道(named pipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。 # 信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 # 消息队列( message queue ) :消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 # 信号( sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。#共享内存( shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。 # 套接字( socket ) :套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。 管道的主要局限性正体现在它的特点上: 只支持单向数据流; 只能用于具有亲缘关系的进程之间; 没有名字; 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等; 2. 用于进程间通讯(IPC)的四种不同技术: 1. 消息传递(管道,FIFO,posix和system v消息队列) 2. 同步(互斥锁,条件变量,读写锁,文件和记录锁,Posix和System V信号灯) 3. 共享内存区(匿名共享内存区,有名Posix共享内存区,有名System V共享内存区) 4. 过程调用(Solaris门,Sun RPC) 消息队列和过程调用往往单独使用,也就是说它们通常提供了自己的同步机制.相反,共享内存区

基于java的进程通信

1 仲恺农业工程学院实验报告纸 计算机科学与工程 (院、系) 网络工程 专业 083 班 组 《操作系统》 实验二 进程通信 一.实验目的: 1、 理解进程消息通信的概念,如何实现两个创建进程之间的数据传递。。 2、 理解进程共享变量的进程通信。 二.实验内容: 1、 选择Window 或Linux ,并选择该操作系统中一种进程通信的方式。 2、 查找该进程通信的API 使用方式,设计出一个合适的应用程序。 3、 采用高级程序语言实现该应用程序。 4、 测试进程通信程序,能够满足微机操作系统中进程之间的通信。 三.实验步骤和过程 1、 进程通信的知识点: (1)、进程通信的概念:进程之间互相交换信息的工作称为进程通信IPC (2)、进程通信的方式:信号(signal )通信机制; 共享存储区 (sharedmemory)通信机制;共享文件(shared file)通信机制;消息传递(message passing)通信机制。 (3)、进程通信机制:管道通信机制,共享主存通信机制,消息传递通信机制。 2、程序设计环境 (1)、Widows7操作系统,eclipse 平台! (2)、套接字(socket )通信 套接字通信,其中一个运行在客户端,称之为ClientSocket ,另一个运行于服务器端面,称为ServerSocket 。根据连接启动的方式以及本地要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听、

2 客户端请求、连接确认。 服务器监听是指服务端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。 客户端请求是由客户端的套接字提出连接请求,要连接的目标是服务器端套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器套接字的地址和端口号,然后再向服务器端套接字提出连接请求。 连接确认是当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的信息发送给客户端,一旦客户端确认了此连接,连接即可建立。而服务器端继续处于监听状态,继续接收其他客户端的连接请求。 使用套接字进行数据处理有两种基本模式:同步和异步。 同步模式: 同步模式的特点是在通过Socket 进行连接、接收、发送数据时,客户机和服务器在接收到对方响应前会出于阻塞状态,即一直等到收到对方请求进才继续执行下面的语句。可见,同步模式只适用于数据处理不太多的场合。当程序执行的任务很多时,长时间的等待可能会让用户无法忍受。 异步模式: 异步模式的特点是在通过Socket 进行连接、接收、发送操作时,客户机或服务器不会处于阻塞方式,而是利用callback 机制进行连接、接收、发送处理,这样就可以在调用发送或接收的方法后直接返回,并继续执行下面的程序。可见,异步套接字特别适用于进行大量数据处理的场合。 使用同步套接字进行编程比较简单,而异步套接字编程则比较复杂。

进程间通信实验报告

进程间通信实验报告 班级:10网工三班学生姓名:谢昊天学号:1215134046 实验目的和要求: Linux系统的进程通信机构 (IPC) 允许在任意进程间大批量地交换数据。本实验的目的是了解和熟悉Linux支持的消息通讯机制及信息量机制。 实验内容与分析设计: (1)消息的创建,发送和接收。 ①使用系统调用msgget (), msgsnd (), msgrev (), 及msgctl () 编制一长度为1k 的消息的发送和接收程序。 ②观察上面的程序,说明控制消息队列系统调用msgctl () 在此起什么作用? (2)共享存储区的创建、附接和段接。 使用系统调用shmget(),shmat(),sgmdt(),shmctl(),编制一个与上述功能相同的程序。(3)比较上述(1),(2)两种消息通信机制中数据传输的时间。 实验步骤与调试过程: 1.消息的创建,发送和接收: (1)先后通过fork( )两个子进程,SERVER和CLIENT进行通信。 (2)在SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER 。SERVER每接收到一个消息后显示一句“(server)received”。 (3)CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,既是 SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。 (4)父进程在 SERVER和 CLIENT均退出后结束。 2.共享存储区的创建,附接和断接: (1)先后通过fork( )两个子进程,SERVER和CLIENT进行通信。 (2)SERVER端建立一个KEY为75的共享区,并将第一个字节置为-1。作为数据空的标志.等待其他进程发来的消息.当该字节的值发生变化时,表示收到了该消息,进行处理.然后再次把它的值设为-1.如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER.SERVER 每接收到一次数据后显示”(server)received”. (3)CLIENT端建立一个为75的共享区,当共享取得第一个字节为-1时, Server端空闲,可发送请求. CLIENT 随即填入9到0.期间等待Server端再次空闲.进行完这些操作后, CLIENT退出. CLIENT每发送一次数据后显示”(client)sent”. (4)父进程在SERVER和CLIENT均退出后结束。 实验结果: 1.消息的创建,发送和接收: 由 Client 发送两条消息,然后Server接收一条消息。此后Client Server交替发送和接收消息。最后一次接收两条消息。Client 和Server 分别发送和接收了10条消息。message 的传送和控制并不保证完全同步,当一个程序不再激活状态的时候,它完全可能继续睡眠,造成上面现象。在多次send message 后才 receive message.这一点有助于理解消息转送的实现机理。

进程控制与进程间通信操作系统实验报告

工程大学实验报告 专业班级:姓名:学号: 课程名称:操作系统 实验成绩:指导教师:蔡敦波 实验名称:进程控制与进程间通信 一、实验目的: 1、掌握进程的概念,明确进程和程序的区别。 2、认识和了解并发执行的实质。 3、了解什么是信号。 4、熟悉LINUX系统中进程之间软中断通信的基本原理。 二、实验内容: 1、进程的创建(必做题) 编写一段程序,使用系统调用fork( )创建两个子进程,在系统中有一个父进程和两个子进程活动。让每个进程在屏幕上显示一个字符;父进程显示字符“a”,子进程分别显示字符“b”和“c”。试观察记录屏幕上的显示结果,并分析原因。 <参考程序>

运行的结果是bca. 首先创建进程p1,向子进程返回0,输出b.又创建进程p2,向子进程返回0,输出c,同时向父进程返回子进程的pid,输出a 2、修改已编写的程序,将每个进程的输出由单个字符改为一句话,再观察程序执行时屏幕上出现的现象,并分析其原因。(必做题) <参考程序> # include int main() { int p1, p2, i; while((p1=fork())= = -1); if(p1= =0) for(i=0;i<500;i++) printf(“child%d\n”,i); else { while((p2=fork())= =-1); If(p2= =0) for(i=0;i<500;i++) printf(“son%d\n”,i); else for(i=0;i<500;i++) printf(“daughter%d\n”,i); } }

运行的结果是如上图所示. 首先创建进程p1,向子进程返回0,并for语句循环输出child +i字符串.又创建进程p2,向子进程返回0,输出字符串son+i,同时向父进程返回子进程的pid,输出字符串duaghter +i ,各打印5次。

进程间的通信

实验三进程间的通信 【实验类型】 综合性实验 【目的要求】 学习如何利用管道机制、消息缓冲队列、共享存储区机制进行进程间的通讯,并加深对上述通信机制的理解。 【内容提要】 1、了解系统调用pipe()、msgget()、msgsnd()、msgrcv ()、msgctl()、shmget()、shmat()、shmdt()、shmctl()的功能和实现过程。 2、编写一段程序,使其用管道来实现父子进程之间的进程通讯。子进程向父进程发送自己的进程标识符,以及字符串“is sending a message to parent!”。父进程则通过管道读出子进程发来的消息,将消息显示在屏幕上,然后终止。 3、编写一段程序,使用系统调用fork()来创建两个子进程CLIENT进程和SERVER进程,使其用消息缓冲队列来实现CLIENT进程和SERVER进程之间的通信。SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER。SERVER每接收到一条消息后显示一句“(server) received”。CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,即是SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client) sent”。父进程在SERVER和CLIENT均退出后结束。 4、编写一个与3具有类似功能的程序,使其用共享存储区来实现两个进程之间的通讯。 【主要仪器设备】 每人一台计算机,硬件要求:CPU PII以上,64M内存,1OOM硬盘空间即可;软件要求: Linux操作系统。

Linux下的进程间通信-详解

Linux下的进程间通信-详解 详细的讲述进程间通信在这里绝对是不可能的事情,而且笔者很难有信心说自己对这一部分内容的认识达到了什么样的地步,所以在这一节的开头首先向大家推荐著 名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX Environment》,它的中文译本《UNIX环境高级编程》已有机械工业出版社出版,原文精彩,译文同样地道,如果你的确对在Linux下编程有浓 厚的兴趣,那么赶紧将这本书摆到你的书桌上或计算机旁边来。说这么多实在是难抑心中的景仰之情,言归正传,在这一节里,我们将介绍进程间通信最最初步和最 最简单的一些知识和概念。 首先,进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法。但一般说来, 进程间通信(IPC:InterProcess Communication)不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系 统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信 方法:管道、消息队列、共享内存、信号量、套接口等等。下面我们将逐一介绍。 2.3.1 管道 管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。 无名管道由pipe()函数创建: #include int pipe(int filedis[2]); 参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。 #define INPUT 0 #define OUTPUT 1 void main() { int file_descriptors[2]; /*定义子进程号 */ pid_t pid; char buf[256]; int returned_count; /*创建无名管道*/ pipe(file_descriptors); /*创建子进程*/ if((pid = fork()) == -1) { printf("Error in fork\n"); exit(1); } /*执行子进程*/ if(pid == 0) { printf("in the spawned (child) process...\n"); /*子进程向父进程写数据,关闭管道的读端*/ close(file_descriptors[INPUT]); write(file_descriptors[OUTPUT], "test data", strlen("test data"));

进程间的通信

# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 # 有名管道(named pipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。 # 信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 # 消息队列( message queue ) :消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 # 信号( sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。# 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。 # 套接字( socket ) :套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。 windows进程通信的几种方式(转) 2008-10-13 16:47 1 文件映射 文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作,只需简单的指针操作就可读取和修改文件的内容。 Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。 应用程序有三种方法来使多个进程共享一个文件映射对象。 (1)继承:第一个进程建立文件映射对象,它的子进程继承该对象的句柄。 (2)命名文件映射:第一个进程在建立文件映射对象时可以给该对象指定一个名字(可与文件名不同)。第二个进程可通过这个名字打开此文件映射对象。另外,第一个进程也可以通过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。 (3)句柄复制:第一个进程建立文件映射对象,然后通过其它IPC机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。 文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。 2 共享内存 Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操作系统页面文件访问内存,其它进程打开该文件映射

04--Linux系统编程-进程间通信

IPC方法 Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。 在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有: ①管道(使用最简单) ②信号(开销最小) ③共享映射区(无血缘关系) ④本地套接字(最稳定) 管道 管道的概念: 管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质: 1. 其本质是一个伪文件(实为内核缓冲区) 2.由两个文件描述符引用,一个表示读端,一个表示写端。 3. 规定数据从管道的写端流入管道,从读端流出。 管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。 管道的局限性: ①数据自己读不能自己写。 ②数据一旦被读走,便不在管道中存在,不可反复读取。 ③由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。 ④只能在有公共祖先的进程间使用管道。

常见的通信方式有,单工通信、半双工通信、全双工通信。 pipe函数 创建管道 int pipe(int pipefd[2]); 成功:0;失败:-1,设置errno 函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd[0] →r;fd[1] →w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。 管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤: 1.父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。 2.父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。 3.父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。 练习:父子进程使用管道通信,父写入字符串,子进程读出并,打印到屏幕。【pipe.c】 思考:为甚么,程序中没有使用sleep函数,但依然能保证子进程运行时一定会读到数据呢? 管道的读写行为 使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志): 1.如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。

进程的消息通信_带答案版

实验二进程管理 2.2 进程的消息通信 1.实验目的 (1) 加深对进程通信的理解,理解进程消息传递机制。 (2) 掌握进程通信相关系统调用。 (3) 理解系统调用和用户命令的区别。 2.实验类型:验证型 3.实验学时:2 4.实验原理和知识点 (1) 实验原理:消息通信机制允许进程之间大批量交换数据。消息通信机制是以消息队列为基础的,消息队列是消息的链表。发送进程将消息挂入接收进程的消息队列,接收进程从消息队列中接收消息。消息队列有一个消息描述符。对消息队列的操作是通过描述符进行的。任何进程,只要有访问权并且知道描述符,就可以访问消息队列。每个消息包括一个正长整型的类型字段,和一个非负长度的数据。进程读或写消息时,要给出消息的类型。若队列中使用的消息类型为0,则读取队列中的第一个消息。 (2) 知识点:消息、消息队列 5.实验环境(硬件环境、软件环境): (1)硬件环境:Intel Pentium III 以上CPU,128MB以上内存,2GB以上硬盘 (2)软件环境:linux操作系统。 6. 预备知识

(1) msgget()系统调用: 头文件#include 函数原型int msgget(key_t key, int flag); 功能:创建消息队列,或返回与key对应的队列描述符。成功返回消息描述符,失败则返回-1。 参数:key是通信双方约定的队列关键字,为长整型数。flag是访问控制命令,它的低9位为访问权限(代表用户、组用户、其他用户的读、写、执行访问权),其它位为队列建立方式。(例:rwxrwx---:111111000) (2) msgsnd()系统调用: 头文件#include 函数原型int msgsnd(int id, struct msgbuf *msgp,int size,int flag); 功能:发送一个消息。成功返回0,失败返回-1。 参数:id是队列描述符。msgp是用户定义的缓冲区。size是消息长度。flag是操作行为,若(flag&IPC_NOWAIT)为真,调用进程立即返回;若(flag&IPC_NOWAIT)为假,调用进程阻塞,直到消息被发送出去或队列描述符被删除或收到中断信号为止。缓冲区结构定义如下:struct msgbuf{ long mtype; char mtext[n]; }; (3) msgrcv()系统调用: 头文件#include 函数原型int msgrcv(int id, struct msgbuf *msgp, int size,int type,int flag); 功能:接收一个消息。成功返回消息正文长度,失败返回-1。

进程间通讯机制

进程间通讯机制 进程在核心的协调下进行相互间的通讯。Linux支持大量进程间通讯(IPC) 机制。除了信号和管道外,Linux 还支持Unix系统V中的IPC机制。 信号 信号是Unix系统中的最古老的进程间通讯方式。它们用来向一个或多个进程发送异步事件信号。信号可以从键盘中断中产生,另外进程对虚拟内存的非法存取等系统错误环境下也会有信号产生。信号还被shell程序用来向其子进程发送任务控制命令。 系统中有一组被详细定义的信号类型,这些信号可以由核心或者系统中其它具有适当权限的进程产生。使用kill命令(kill -l)可以列出系统中所有已经定义的信号。在我的系统(Intel系统)上运行结果如下: 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGIOT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 当我在Alpha AXP中运行此命令时,得到了不同的信号个数。除了两个信号外,进程可以忽略这些信号中的绝大部分。其一是引起进程终止执行的SIGSTOP信号,另一个是引起进程退出的SIGKILL信号。至于其它信号,进程可以选择处理它们的具体方式。进程可以阻塞信号,如若不阻塞,则可以在自行处理此信号和将其转交核心处理之间作出选择。如果由核心来处理此信号,它将使用对应此信号的缺省处理方法。比如当进程接收到SIGFPE(浮点数异常)时,核心的缺省操作是引起core dump和进程的退出。信号没有固有的相对优先级。如果在同一时刻对于一个进程产生了两个信号,则它们将可能以任意顺序到达进程并进行处理。

进程间通信的几种方式

进程间通信的几种方式 2009-06-19 23:28 (1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。 (2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。 (3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。 (4)消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺 (5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。 (6)内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。 (7)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。 (8)套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字

相关文档
最新文档