📄 进程和线程编程.htm
字号:
<P> 下面的程序可以用来初始化一个新的信号量值:
<P>void init_semaphore(int sid,int semnum,int initval)<BR>{<BR>union
semunsemopts;<BR>semopts.val=initval;<BR>semctl(sid,semnum,SETVAL,semopts);<BR>}
<P> 注意系统调用semctl中的最后一个参数是一个联合类型的副本,而不是一个指向联合类型的指针。
<P>
<P>
<P>
<CENTER><A
href="http://www.huihoo.com/joyfire.net/11.html#Content">[目录]</A></CENTER>
<HR>
<BR><A id=I255 name=I255></A>
<CENTER><B><FONT size=+2>共享内存</FONT></B></CENTER><BR>
共享内存就是由几个进程共享一段内存区域。这可以说是最快的IPC形式,因为它无须任何的中间操作(例如,管道、消息队列等)。它只是把内存段直接映射到调用进程的地址空间中。这样的内存段可以是由一个进程创建的,然后其他的进程可以读写此内存段。
<P>
每个系统的共享内存段在系统内核中也保持着一个内部的数据结构shmid_ds。此数据结构是在linux/shm.h中定义的,如下所示:
<P>/* One shmid data structure for each shared memory segment in the system.
*/<BR>struct shmid_ds {<BR>struct ipc_perm shm_perm; /* operation perms
*/<BR>int shm_segsz; /* size of segment (bytes) */<BR>time_t shm_atime; /* last
attach time */<BR>time_t shm_dtime; /* last detach time */<BR>time_t shm_ctime;
/* last change time */<BR>unsigned short shm_cpid; /* pid of creator
*/<BR>unsigned short shm_lpid; /* pid of last operator */<BR>short shm_nattch;
/* no. of current attaches */<BR>/* the following are private */<BR>unsigned
short shm_npages; /* size of segment (pages) */<BR>unsigned long *shm_pages; /*
array of ptrs to frames -> SHMMAX */<BR>struct vm_area_struct *attaches; /*
descriptors for attaches */<BR>};
<P>shm_perm 是数据结构ipc_perm的一个实例。这里保存的是内存段的存取权限,和其他的有关内存段创建者的信息。<BR>shm_segsz
内存段的字节大小。<BR>shm_atime 最后一个进程存取内存段的时间。<BR>shm_dtime 最后一个进程离开内存段的时间。<BR>shm_ctime
内存段最后改动的时间。<BR>shm_cpid 内存段创建进程的P I D。<BR>shm_lpid 最后一个使用内存段的进程的P I
D。<BR>shm_nattch 当前使用内存段的进程总数。<BR>
<CENTER><A
href="http://www.huihoo.com/joyfire.net/11.html#Content">[目录]</A></CENTER>
<HR>
<BR><A id=I256 name=I256></A>
<CENTER><B><FONT
size=+2>shmget()</FONT></B></CENTER><BR>系统调用:shmget();<BR>原型:int shmget(key_t
key,int size,int
shmflg);<BR>返回值:如果成功,返回共享内存段标识符。如果失败,则返回-1:errno=EINVAL(无效的内存段大小)<BR>EEXIST(内存段已经存在,无法创建)<BR>EIDRM(内存段已经被删除)<BR>ENOENT(内存段不存在)<BR>EACCES(权限不够)<BR>ENOMEM(没有足够的内存来创建内存段)
<P>
系统调用shmget()中的第一个参数是关键字值(它是用系统调用ftok()返回的)。其他的操作都要依据shmflg中的命令进行。<BR>
·IPC_CREAT如果系统内核中没有共享的内存段,则创建一个共享的内存段。<BR>
·IPC_EXCL当和IPC_CREAT一同使用时,如果共享内存段已经存在,则调用失败。<BR>
当IPC_CREAT单独使用时,系统调用shmget()要么返回一个新创建的共享内存段的标识符,要么返回一个已经存在的共享内存段的关键字值。如果IPC_EXCL和IPC_CREAT一同使用,则要么系统调用新创建一个共享的内存段,要么返回一个错误值-1。IPC_EXCL单独使用没有意义。
<P> 下面是一个定位和创建共享内存段的程序:
<P>int open_segment(key_t keyval,int segsize)<BR>{<BR>int
shmid;<BR>if((shmid=shmget(keyval,segsize,IPC_CREAT|0660))==-1)<BR>{<BR>return(-1);<BR>}<BR>return(shmid);<BR>}
<P> 一旦一个进程拥有了一个给定的内存段的有效IPC标识符,它的下一步就是将共享的内存段映射到自己的地址空间中。
<P>
<P>
<P>
<P>
<CENTER><A
href="http://www.huihoo.com/joyfire.net/11.html#Content">[目录]</A></CENTER>
<HR>
<BR><A id=I257 name=I257></A>
<CENTER><B><FONT size=+2>shmat()</FONT></B></CENTER><BR>系统调用: shmat();<BR>原型:int
shmat ( int shmid, char *shmaddr, int
shmflg);<BR>返回值:如果成功,则返回共享内存段连接到进程中的地址。如果失败,则返回- 1:errno = EINVAL (无效的IPC ID
值或者无效的地址)<BR>ENOMEM (没有足够的内存)<BR>EACCES (存取权限不够)
<P> 如果参数a d d
r的值为0,那么系统内核则试图找出一个没有映射的内存区域。我们推荐使用这种方法。你可以指定一个地址,但这通常是为了加快对硬件设备的存取,或者解决和其他程序的冲突。<BR>
下面的程序中的调用参数是一个内存段的I P C标识符,返回内存段连接的地址:
<P>char *attach_segment(int shmid)<BR>{<BR>return(shmat(shmid, 0, 0));<BR>}
<P>
一旦内存段正确地连接到进程以后,进程中就有了一个指向该内存段的指针。这样,以后就可以使用指针来读取此内存段了。但一定要注意不能丢失该指针的初值。
<P>
<P>
<P>
<CENTER><A
href="http://www.huihoo.com/joyfire.net/11.html#Content">[目录]</A></CENTER>
<HR>
<BR><A id=I258 name=I258></A>
<CENTER><B><FONT size=+2>shmctl()</FONT></B></CENTER><BR>系统调用:shmctl ( )
;<BR>原型:int shmctl( int shmqid, int cmd, struct shmid_ds *buf );<BR>返回值: 0
,如果成功。<BR>-1,如果失败:errno = EACCES
(没有读的权限,同时命令是IPC_STAT)<BR>EFAULT(buf指向的地址无效,同时命令是IPC_SET和IPC_STAT )<BR>EIDRM
(内存段被移走)<BR>EINVAL (shmqid 无效)<BR>EPERM (使用IPC_SET 或者IPC_RMID
命令,但调用进程没有写的权限)<BR>IPC_STAT 读取一个内存段的数据结构shmid_ds,并将它存储在buf参数指向的地址中。<BR>IPC_SET
设置内存段的数据结构shmid_ds中的元素ipc_perm的值。从参数buf中得到要设置的值。<BR>IPC_RMID 标志内存段为移走。
<P>
命令IPC_RMID并不真正从系统内核中移走共享的内存段,而是把内存段标记为可移除。进程调用系统调用shmdt()脱离一个共享的内存段。
<P>
<P>
<P>
<CENTER><A
href="http://www.huihoo.com/joyfire.net/11.html#Content">[目录]</A></CENTER>
<HR>
<BR><A id=I259 name=I259></A>
<CENTER><B><FONT
size=+2>shmdt()</FONT></B></CENTER><BR>系统调用:shmdt();<BR>调用原型:int shmdt ( char
*shmaddr );<BR>返回值:如果失败,则返回- 1:errno = EINVAL (无效的连接地址)
<P>
当一个进程不在需要共享的内存段时,它将会把内存段从其地址空间中脱离。但这不等于将共享内存段从系统内核中移走。当进程脱离成功后,数据结构shmid_ds中元素shm_nattch将减1。当此数值减为0以后,系统内核将物理上把内存段从系统内核中移走。
<P>
<P>
<P>
<CENTER><A
href="http://www.huihoo.com/joyfire.net/11.html#Content">[目录]</A></CENTER>
<HR>
<BR><A id=I260 name=I260></A>
<CENTER><B><FONT size=+2>线程</FONT></B></CENTER><BR>
线程通常叫做轻型的进程。虽然这个叫法有些简单化,但这有利于了解线程的概念。因为线程和进程比起来很小,所以相对来说,线程花费更少的CPU资源。进程往往需要它们自己的资源,但线程之间可以共享资源,所以线程更加节省内存。Mach的线程使得程序员可以编写并发运行的程序,而这些程序既可以运行在单处理器的机器上,也可以运行在多处理器的机器中。另外,在单处理器环境中,当应用程序执行容易引起阻塞和延迟的操作时,线程可以提高效率。
<P>
用子函数pthread_create创建一个新的线程。它有四个参数:一个用来保存线程的线程变量、一个线程属性、当线程执行时要调用的函数和一个此函数的参数。例如:<BR>pthread_ta_thread
;<BR>pthread_attr_ta_thread_attribute ;<BR>void thread_function(void
*argument);<BR>char * some_argument;<BR>pthread_create( &a_thread,
a_thread_attribute, (void *)&thread_function,<BR>(void *)
&some_argument);<BR>
线程属性只指明了需要使用的最小的堆栈大小。在以后的程序中,线程的属性可以指定其他的值,但现在大部分的程序可以使用缺省值。不像UNIX系统中使用fork系统调用创建的进程,它们和它们的父进程使用同一个执行点,线程使用在pthread_create中的参数指明要开始执行的函数。
<P> 现在我们可以编制第一个程序了。我们编制一个多线程的应用程序,在标准输出中打印“Hello Wo r l
d”。首先我们需要两个线程变量,一个新线程开始执行时可以调用的函数。我们还需要指明每一个线程应该打印的信息。一个做法是把要打印的字符串分开,给每一个线程一个字符串作为开始的参数。请看下面的代码:<BR>void
print_message_function( void *ptr );<BR>main( )<BR>{<BR>pthread_t thread1,
thread2;<BR>char *message1 = "Hello";<BR>char *message2 = "Wo r l d "
;<BR>pthread_create( &thread1,
pthread_attr_default,<BR>(void*)&print_message_function, (void*)
message1);<BR>pthread_create(&thread2,
pthread_attr_default,<BR>(void*)&print_message_function, (void*)
message2);<BR>exit( 0 ) ;<BR>}<BR>void print_message_function( void *ptr
)<BR>{<BR>char *message;<BR>message = (char *) ptr;<BR>printf("%s ",
message);<BR>}
<P>
程序通过调用pthread_create创建第一个线程,并将“Hello”作为它的启动参数。第二个线程的参数是“World”。当第一个线程开始执行时,它使用参数“Hello”执行函数print_message_function。它在标准输出中打印“Hello”,然后结束对函数的调用。线程当离开它的初始化函数时就将终止,所以第一个线程在打印完“Hello”后终止。当第二个线程执行时,它打印“World”然后终止。但这个程序有两个主要的缺陷。<BR>
首先也是最重要的是线程是同时执行的。这样就无法保证第一个线程先执行打印语句。所以你很可能在屏幕上看到“World Hello”,而不是“Hello
World”。请注意对exit的调用是父线程在主程序中使用的。这样,如果父线程在两个子线程调用打印语句之前调用exit,那么将不会有任何的打印输出。这是因为exit函数将会退出进程,同时释放任务,所以结束了所有的线程。任何线程(不论是父线程或者子线程)调用exit
都会终止所有其他线程。如果希望线程分别终止,可以使用pthread_exit函数。<BR>我们可以使用一个办法弥补此缺陷。我们可以在父线程中插入一个延迟程序,给子线程足够的时间完成打印的调用。同样,在调用第二个之前也插入一个延迟程序保证第一个线程在第二个线程执行之前完成任务。
<P>void print_message_function( void *ptr );<BR>main ( )<BR>{<BR>pthread_t
thread1, thread2;<BR>char *message1 = "Hello”;<BR>char *message2 = "Wo r l d "
;<BR>pthread_create( &thread1, pthread_attr_default,<BR>(void *)
&print_message_function, (void *) message1);<BR>sleep (10)
;<BR>pthread_create(&thread2, pthread_attr_default,<BR>(void *)
&print_message_function, (void *) message2);<BR>sleep ( 10 ) ;<BR>exit (0)
;<BR>}<BR>void print_message_function( void *ptr )<BR>{<BR>char
*message;<BR>message = (char *) ptr;<BR>printf("%s",
message);<BR>pthread_exit(0) ;<BR>}
<P>
这样是否达到了我们的要求了呢?不尽如此,因为依靠时间的延迟执行同步是不可靠的。这里遇到的情形和一个分布程序和共享资源的情形一样。共享的资源是标准的输出设备,分布计算的程序是三个线程。<BR>其实这里还有另外一个错误。函数sleep和函数e
x i
t一样和进程有关。当线程调用sleep时,整个的进程都处于睡眠状态,也就是说,所有的三个线程都进入睡眠状态。这样我们实际上没有解决任何的问题。希望使一个线程睡眠的函数是pthread_delay_np。例如让一个线程睡眠2秒钟,用如下程序:
<P>struct timespec delay;<BR>delay.tv_sec = 2;<BR>delay.tv_nsec =
0;<BR>pthread_delay_np( &delay );<BR>}<BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -