Chinaunix首页 | 论坛 | 博客
  • 博客访问: 240397
  • 博文数量: 27
  • 博客积分: 832
  • 博客等级: 准尉
  • 技术积分: 336
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-20 20:47
文章存档

2012年(9)

2010年(10)

2009年(7)

2008年(1)

我的朋友

分类: LINUX

2010-06-24 11:32:37


HoxmDocument-Study


Linux Posix Thread Programming

V0.1
2010/06/24


踏踏实实做人,认认真真做事,实力沉淀于积累,能力提升自总结,
追逐每天升起的太阳, 挥洒汗水,放飞心情。

内容预览

   1. 内容目录
   2. 一、基础介绍
         1. 进程和线程介绍
         2. Pthread介绍
   3. 二、线程函数
         1. creat
         2. exit和cancel
         3. join和detach
         4. self和equal
         5. once
         6. kill
   4. 三、互斥锁函数
         1. 创建互斥锁
         2. 注销互斥锁
         3. 获取互斥锁
         4. 释放互斥锁
         5. 互斥锁属性
         6. 互斥锁说明
   5. 四、条件变量
         1. 创建条件变量
         2. 注销条件变量
         3. 等待条件变量
         4. 触发条件变量
         5. 条件变量使用实例
   6. 附录一 信号量函数
         1. 打开有名信号量
         2. 删除有名信号量
         3. 创建无名信号量
         4. 删除无名信号量
         5. 信号量操作
   7. 附录二 共享内存
   8. 附录三 消息队列
   9. 附录四 进程函数
         1. 进程创建
         2. exec( )函数族
         3. 进程函数大全(未整理)
  10. 附录五 参考资料


一、基础介绍

进程和线程介绍

Processes contain information about program resources and program execution state, including:

  1. Process ID, process group ID, user ID, and group ID

  2. Environment

  3. Working directory.

  4. Program instructions

  5. Registers

  6. Stack

  7. Heap

  8. File descriptors

  9. Signal actions

  10. Shared libraries

  11. Inter-process communication tools (such as message queues, pipes, semaphores, or shared memory).


Thread operations include thread creation, termination, synchronization (joins,blocking), scheduling, data management and process interaction.

A thread does not maintain a list of created threads, nor does it know the thread that created it.

All threads within a process share the same address space.

Threads in the same process share:

  1. Process instructions

  2. Most data

  3. open files (descriptors)

  4. signals and signal handlers

  5. current working directory

  6. User and group id


Each thread has a unique:


  1. Thread ID

  2. set of registers

  3. stack pointer , stack for local variables, return addresses

  4. signal mask, Set of pending and blocked signals

  5. priority

  6. Scheduling properties (such as policy or priority)

  7. Thread specific data.

  8. Return value: errno


pthread functions return "0" if OK.



线 程和进程十分相似,不同的只是线程比进程小。首先,线程采用了多个线程可共享资源的设计思想;例如,它们的操作大部分都是在同一地址空间进行的。其次,从 一个线程切换到另一线程所花费的代价比进程低。再次,进程本身的信息在内存中占用的空间比线程大,因此线程更能允分地利用内存。

Pthread介绍

Pthread(POSIX 线程接口, IEEE POSIX 1003.1c standard (1995))是一套通用的线程库, 它广泛的被各种Unix所支持, 是由POSIX提出的. 因此, 它具有很好的客移植性. Pthread本来是一套用户级线程库, 但在Linux上实现时, 却使用了内核级线程来完成, 这样的好处是, 可以充分的提高程序的并发性, 线程也可以象以前一样调用Head这样的函数, 而不必担心会由于阻塞影响其它的线程的运行. 但这样一来, linux的线程就不是标准的了。


Pthread提供以下几类函数

  1. Thread management APIs

  2. Thread specific storage APIs

  3. Thread cancellation APIs

  4. Mutex synchronization API

  5. Condition variable synchronization APIs

  6. Read/write lock synchronization APIs

  7. Signals APIs



二、线程函数

线程相关函数主要有:


头文件: #include

函数名称

函数说明

pthread_create

create a new thread

pthread_exit

terminate the calling thread

pthread_cancel

thread cancellation

pthread_detach

put a running thread in the detached state

pthread_join

wait for termination of another thread

pthread_self

return identifier of current thread

pthread_equal

compare two thread identifiers

pthread_once

once-only initialization





pthread_attr_init


pthread_attr_destroy


pthread_attr_getdetachstate


pthread_attr_getinheritsched


pthread_attr_getschedparam


pthread_attr_getschedpolicy


pthread_attr_getscope


pthread_attr_setdetachstate


pthread_attr_setinheritsched


pthread_attr_setschedparam


pthread_attr_setschedpolicy


pthread_attr_setscope




pthread_atfork

register handlers to be called at fork(2) time








pthread_cleanup_pop [pthread_cleanup_push] - install and remove cleanup handlers

pthread_cleanup_pop_restore_np [pthread_cleanup_push] - install and remove cleanup handlers

pthread_cleanup_push - install and remove cleanup handlers

pthread_cleanup_push_defer_np [pthread_cleanup_push] - install and remove cleanup handlers


pthread_getschedparam [pthread_setschedparam] - control thread scheduling parameters

pthread_getspecific [pthread_key_create] - management of thread-specific data


pthread_key_create - management of thread-specific data

pthread_key_delete [pthread_key_create] - management of thread-specific data

pthread_kill_other_threads_np - terminate all threads in program except calling thread

pthread_kill [pthread_sigmask] - handling of signals in threads


pthread_setcancelstate [pthread_cancel] - thread cancellation

pthread_setcanceltype [pthread_cancel] - thread cancellation

pthread_setschedparam - control thread scheduling parameters

pthread_setspecific [pthread_key_create] - management of thread-specific data

pthread_sigmask - handling of signals in threads

pthread_testcancel [pthread_cancel] - thread cancellation

creat

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

Arguments:

thread - returns the thread id. (unsigned long int defined in bits/pthreadtypes.h)

attr - Set to NULL if default thread attributes are used. (else define members of the struct pthread_attr_t defined in bits/pthreadtypes.h) Attributes include:

detached state (joinable? ) Default:PTHREAD_CREATE_JOINABLE

PTHREAD_CREATE_DETACHED

scheduling policy (real-time? PTHREAD_INHERIT_SCHED,

PTHREAD_EXPLICIT_SCHED,

SCHED_OTHER)

scheduling parameter

inheritsched attribute Default: PTHREAD_EXPLICIT_SCHED

Inherit from parent thread: PTHREAD_INHERIT_SCHED

scope Kernel threads: PTHREAD_SCOPE_SYSTEM

User threads: PTHREAD_SCOPE_PROCESS Pick one or the other not both.)

guard size

stack address See unistd.h and bits/posix_opt.h _POSIX_THREAD_ATTR_STACKADDR)

stack size default minimum PTHREAD_STACK_SIZE set in pthread.h

void * (*start_routine)(void* arg) - pointer to the function to be threaded.

*arg - pointer to argument of function. To pass multiple arguments, send a pointer to a structure.


exit和cancel

退出或结束线程的方式有如下几种:

  1. 线程函数返回

  2. 线程所在的进程退出

  3. 线程自身调用pthread_exit 退出

  4. 其他线程调用pthread_cancel使其退出




void pthread_exit(void *value_ptr);

Notes:

  1. pthread_exit is used to explicitly exit a thread. Typically, the pthread_exit() routine is called after a thread has completed its work and is no longer required to exist.

  2. If main() finishes before the threads it has created, and exits with pthread_exit(), the other threads will continue to execute. Otherwise, they will be automatically terminated when main() finishes.

  3. The programmer may optionally specify a termination status, which is stored as a void pointer for any thread that may join the calling thread.

  4. Cleanup: the pthread_exit() routine does not close files; any files opened inside the thread will remain open after the thread is terminated.

  5. Discussion: In subroutines that execute to completion normally, you can often dispense with calling pthread_exit() - unless, of course, you want to pass a return code back. However, in main(), there is a definite problem if main() completes before the threads it spawned. If you don't call pthread_exit() explicitly, when main() completes, the process (and all threads) will be terminated. By calling pthread_exit() in main(), the process and all of its threads will be kept alive even though all of the code in main() has been executed.


int pthread_cancel(pthread_t thread)

给线程发送取消信号,使线程从取消点退出。posix的线程有两中取消模式,立即取消和延迟取消。

  1. 立即取消是你调用pthread_cancel的时候,不管线程当前正在干什么,马上被结束掉.

  2. 延迟取消是在你调用pthread_cancel以后,线程运行到一个取消点函数的时候才会结束

pthread_cancel并不等待线程终止,它仅仅提出请求。

join和detach

int pthread_join(pthread_t thread, void **value_ptr);

当 一个线程通过调用pthread_exit退出或者简单地从启动历程中返回时,进程中的其他线程可以通过调用pthread_join函数获得进程的退出 状态。调用pthread_join进程将一直阻塞,直到指定的线程调用pthread_exit,从启动例程中或者被取消。如果线程只是从它的启动历程 返回,rval_ptr将包含返回码。


int pthread_detach(pthread_t thread);

在 默认情况下,线程的终止状态会保存到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。 当线程被分离时,并不能用pthread_join函数等待它的终止状态。对分离状态的线程进行pthread_join的调用会产生失败,返回 EINVAL.pthread_detach调用可以用于使线程进入分离状态。


self和equal


once

int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))


LinuxThreads 使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执 行过。如果once_control的初值不是PTHREAD_ONCE_INIT(LinuxThreads定义为0),pthread_once() 的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER(0)、IN_PROGRESS(1)、DONE (2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once ()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0。


kill

int pthread_kill(pthread_t thread, int sig);

向指定线程发送信号,信号为0时用于检查此线程ID的线程是否存活。


三、互斥锁函数

互斥锁的主要用途:

  1. 保证多个线程使用独占性资源时的同步。体现资源使用的互斥关系。

  2. 保证函数的可重入安全性,确保一次只有一个线程执行函数的临界代码段。


在Posix Thread中定义有一套专门用于线程同步的mutex函数。

头文件:#include

编译时添加 –lpthread选项:gcc –lpthread –lrt –o test test.c

函数名称

函数说明

pthread_mutex_init

创建mutex

pthread_mutex_destroy

注销mutex

pthread_mutex_lock

获取mutex,失败阻塞

pthread_mutex_trylock

获取mutex,失败立即出错返回

pthread_mutex_timedlock

获取mutex,失败timeout出错返回

pthread_mutex_unlock

释放mutex


创建互斥锁

有两种方法创建互斥锁,静态方式和动态方式。


int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)

动 态方式:mutex 为返回的句柄,mutexattr 设置为NULL 即可(生成默认属性的mutex,只能进程内各个线程同步的普通锁类型),mutexattr 中包含pshared属性,定义mutex是线程间同步还是可以跨进程同步,但是由于mutex基于内存,进程间要共享还是需要共享内存等辅助机制,比较 复杂,暂时就认为mutex只能作为线程同步好了,也不用修改默认属性,等以后有深入了解和知道其他替代方式再修正这个简化的决定。


pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER

静态方式:在LinuxThreads实现中,pthread_mutex_t是一个结构,PTHREAD_MUTEX_INITIALIZER是POSIX定义了一个结构常量。

注销互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex)

销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。

获取互斥锁

int pthread_mutex_lock(pthread_mutex_t *mutex)

int pthread_mutex_trylock(pthread_mutex_t *mutex)

int pthread_mutex_timedlock (pthread_mutex_t *mutex, struct timespec *time)

在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。

pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。pthread_mutex_timelock() 的超时时间设置参考如下:

#include


struct timespec tm;

clock_gettime(CLOCK_REALTIME, &tm);

tm.tv_sec += sdTimeout/1000;

tm.tv_nsec += (sdTimeout%1000)*1000000;


释放互斥锁

int pthread_mutex_unlock(pthread_mutex_t *mutex)

  对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。

互斥锁属性

要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改。下表列出了用来处理互斥锁属性的函数。


函数名称

函数说明

pthread_mutexattr_init

初始化互斥锁属性对象

pthread_mutexattr_destroy

销毁互斥锁属性对象

pthread_mutexattr_setpshared

设置互斥锁范围

pthread_mutexattr_getpshared

获取互斥锁范围

pthread_mutexattr_settype

设置互斥锁的类型属性

pthread_mutexattr_gettype

获取互斥锁的类型属性

pthread_mutexattr_setprotocol

设置互斥锁属性的协议

pthread_mutexattr_getprotocol

获取互斥锁属性的协议

pthread_mutexattr_setprioceiling

设置互斥锁属性的优先级上限

pthread_mutexattr_getprioceiling

获取互斥锁属性的优先级上限

pthread_mutex_setprioceiling

设置互斥锁的优先级上限

pthread_mutex_getprioceiling

获取互斥锁的优先级上限

pthread_mutexattr_setrobust_np

设置互斥锁的强健属性

pthread_mutexattr_getrobust_np

获取互斥锁的强健属性


互斥锁说明

POSIX 线程锁机制的Linux实现都不是取消点,因此,延迟取消类型的线程不会因收到取消信号而离开加锁等待。值得注意的是,如果线程在加锁后解锁前被取消,锁 将永远保持锁定状态,因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。

这个锁机制同时也不是异步信号安全的,也就是说,不应该在信号处理过程中使用互斥锁,否则容易造成死锁。

互斥锁的另一个重要问题是避免死锁。死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西. 总体来讲, 有几个不成文的基本原则:

  1. 一般加锁之后同时要负责谁解,对共享资源操作前一定要获得锁. 完成操作以后一定要释放锁.

  2. 尽量短时间地占用锁.

  3. 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC.

  4. 线程错误返回时应该释放它所获得的锁.


四、条件变量

条件变量的用途:线程间有效

  1. 条件满足的同步和通知,循环检测某种条件是否满足,不满足则阻塞等待通知

  2. 状态改变的通知,改变状态的线程发送触发信号,另外线程等待并判读状态是否改为所设定的情况。


条件变量函数

头文件:#include

编译时添加 –lpthread选项:gcc –lpthread –lrt –o test test.c

函数定义

函数说明

pthread_cond_init

初始化条件变量

pthread_cond_destroy

注销条件变量

pthread_cond_wait

等待条件变量,不成立阻塞

pthread_cond_timedwait

等待条件变量,不成立timeout退出

pthread_cond_signal

触发条件变量,其中一个线程有效

pthread_cond_broadcast

触发条件变量,全部等待的线程有效


注 意:pthread_cond_init,pthread_cond_signal,pthread_cond_broadcast,pthread_cond_wait 只返回0,不会返回其他错误码。也就是说这几个函数每次调用都会成功,编程时不用检查返回值。但pthread_cond_timedwait和 pthread_cond_destroy会返回错误码,需要注意!

创建条件变量

条件变量也有动态和静态两种创建方式

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

动态初始化方式, 该函数的第二个参数指向条件变量属性的结构体。尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常设置为NULL。


pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

静态分配的条件变量,使用默认的条件变量属性进行初始化。PTHREAD_COND_INITIALIZER是一个pthread_cond_t类型的结构体常量。

注销条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

返回错误码:EBUSY。当前还有线程在该条件变量上等待。

pthread_cond_destroy销毁一个条件变量,但只有在没有线程在该条件变量上等待的时候才能销毁,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。

等待条件变量

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);


pthread_cond_wait调用相当复杂,它是如下执行序列的一个组合:

(1)释放互斥锁 并且 线程挂起(这两个操作是一个原子操作);

(2)线程获得信号,尝试获得互斥锁后被唤醒;


我 们知道调用pthread_cond_signal给条件变量发送信号时,如果当时没有线程在等待这个条件变量,信号将被丢弃。如果"释放互斥锁"和"线 程挂起"不是一个原子操作,那么pthread_cond_wait线程在"释放互斥锁"和"线程挂起"之间,如果有信号一旦发生,程序就会错失一次条件 变量变化。


pthread_cond_timewait是等待条件变量的令一种形式,与前一种的区别是计时等待方式如果在给定时刻前条件没有满足,就返回ETIMEOUT结束等待。该函数在不同情况下会返回特定错误码,编程时请参照开发。

触发条件变量

int pthread_cond_signal(pthread_cond_t *cond);

唤醒等待改条件变量所有线程中的一个。如果此时没有线程在等待该条件变量,那么就丢弃该信号,当做什么也没发生。如果有多个线程在等待,精确保证只有一个线程被唤醒。


int pthread_cond_broadcast(pthread_cond_t *cond);

唤醒等待该条件变量所有线程。如果此时没有线程在等待该条件变量,那么就丢弃该信号,当做什么也没发生。

条件变量使用实例


pthread_mutex_t count_mutex;

pthread_cond_t count_threshold_cv;


pthread_mutex_lock(&count_mutex);

while(count

pthread_cond_wait(&count_threshold_cv, &count_mutex);

}

pthread_mutex_unlock(&count_mutex);



pthread_mutex_lock(&count_mutex);

count++;

if (count == COUNT_LIMIT) {

pthread_cond_signal(&count_threshold_cv);

}

pthread_mutex_unlock(&count_mutex);



附录一 信号量函数

信号量的主要用途

  1. 用于生产消费模式,用作于资源计数和资源使用的同步,强调先生产后使用的同步关系。

  2. 强调各个线程间对不同事件处理的先后等待等同步关系

  3. 0/1二值型号量可以简化为mutex的作用


Linux 信号量函数有posix标准的和非posix标准的两套函数接口,其中posix信号量又分为有名信号量和无名信号量。由于posix函数使用起来更方 便,同时和其他操作系统的使用习惯兼容更好,成为首选。这里对非posix的信号量函数(semget, semop, semctl)不做详细介绍。


Posix 信号量函数

头文件:#include

#include /*open 的flag需要用到*/

编译时添加 –lpthread选项:gcc –lpthread –lrt –o test test.c

函数定义

函数说明

sem_open

打开有名信号量

sem_init

无名信号量初始化

sem_close

关闭有名信号量

sem_destroy

无名信号量注销

sem_unlink

从系统中删除有名信号量

sem_wait

获取信号量资源,没有则阻塞

sem_trywait

获取信号量资源,没有则出错退出

sem_timedwait

获取信号量资源,没有则timeout除错退出

sem_post

释放信号量资源

sem_getvalue

返回信号量当前值


有名信号量:基于系统绑定的,和访问文件系统的概念类似,所以可以在各个进程间共享同步。

无名信号量:基于进程内存空间创建,因为进程间内存空间不共享,所以不能进程间共享同步,除非利用共享内存的办法


打开有名信号量

sem_t *sem_open(const char *name, int oflag, [ mode_t mode, unsigned int value ])

name 信号灯的外部名字, 改名字和系统中的信号量绑定。

oflag 参数可以是0、O_CREAT(创建一个信号灯)或O_CREAT|O_EXCL(如果没有指定的信号灯就创建),如果指定了O_CREAT,那么第三个 和第四个参数是需要的;所需信号灯已存在条件下指定O_CREAT不是一个错误。该标志的意思仅仅是“如果所需信号灯尚未存在,那就创建并初始化它”。但 是所需信号灯等已存在条件下指定O_CREAT|O_EXCL却是一个错误。

mode参数指定权限位

value参数指定信号 灯的初始值,通常用来指定共享资源的书面。该初始不能超过SEM_VALUE_MAX,这个常值必须低于为32767。二值信号灯的初始值通常为1,计数 信号灯的初始值则往往大于1。如果指定了O_CREAT(而没有指定O_EXCL),那么只有所需的信号灯尚未存在时才初始化它。


sem_open返回指向sem_t信号灯的指针,该结构里记录着当前共享资源的数目。


实例:

sem_t *sem=sem_open((char*)“semname”, O_CREAT|O_EXCL, 0644, 1);


删除有名信号量

int sem_close(sem_t *sem);

int sem_unlink(count char *name);

一 个进程终止时,内核还对其上仍然打开着的所有有名信号灯自动执行sem_close操作。不论该进程是自愿终止的还是非自愿终止的,这种自动关闭都会发 生。但应注意的是关闭一个信号灯并没有将它从系统中删除。这就是说,Posix有名信号灯至少是随内核持续的:即使当前没有进程打开着某个信号灯,它的值 仍然保持。

要想从系统中删除掉有名信号量,使用sem_unlink。每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。


创建无名信号量

int sem_init(sem_t *sem, int shared, unsigned int value);


基 于内存的信号灯是由sem_init初始化的。sem参数指向必须由应用程序分配的sem_t变量。如果shared为0,那么待初始化的信号灯是在同一 进程的各个线程共享的,否则该信号灯是在进程间共享的。当shared为零时,该信号灯必须存放在即将使用它的所有进程都能访问的某种类型的共享内存中。 跟sem_open一样,value参数是该信号灯的初始值。


注意:posix基于内存的信号灯和posix有名信号灯有一些区别,我们必须注意到这些。

  1. sem_open不需要类型与shared的参数,有名信号灯总是可以在不同进程间共享的。

  2. sem_init不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号灯的值。因此,对于一个给定的信号灯,我们必须小心保证只调用一次sem_init。

  3. sem_open返回一个指向某个sem_t变量的指针,该变量由函数本身分配并初始化。但sem_init的第一个参数是一个指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_init函数初始化。

  4. posix 有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。posix基于内存的信 号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程共享的,那么该信号灯就是随进程持续的,当该进程终止时它也会消失。如果某个基于 内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,只要该共享内存区存在,该信号灯就存在。

  5. 基于内存的信号灯应用于进程很麻烦(需要放到共享内存区域),而有名信号灯却很方便,基于内存的信号灯比较适合应用于一个进程的多个线程。


删除无名信号量

int sem_destroy (sem_t *sem)

使用完一个基于内存的信号灯后,我们调用sem_destroy关闭它。


信号量操作

int sem_wait (sem_t *sem);

int sem_trywait (sem_t *sem);

int sem_timedwait (sem_t *sem, struct timespec *time);

int sem_post (sem_t *sem);

int sem_getvalue (sem_t *sem, int *val);


#include


struct timespec tm;

clock_gettime(CLOCK_REALTIME, &tm);

tm.tv_sec += sdTimeout/1000;

tm.tv_nsec += (sdTimeout%1000)*1000000;


上述操作可以同时应用于有名信号灯和基于内存的无名信号灯。



附录二 共享内存

posix共享内存区涉及两个步骤:

1、指定一个名字参数调用shm_open,以创建一个新的共享内存区对或打开一个以存在的共享内存区。

2、调用mmap把这个共享内存区映射到调用进程的地址空间。传递给shm_open的名字参数随后由希望共享该内存区的任何其他进程使用。


头文件:#include

注意编译程序我们要加上“-lrt”参数。


int shm_open(const char *name,int oflag,mode_t mode);

oflag参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC.

mode参数指定权限位,它指定O_CREAT标志的前提下使用。

shm_open的返回值是一个整数描述字,它随后用作mmap的第五个参数。


int shm_unlink(const char *name);



int ftruncate(int fd,off_t length);

普通文件或共享内存区对象的大小都可以通过调用ftruncate修改。


int stat(const char *file_name,struct stat *buf);

对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。

struct stat{

mode_t st_mode;

uid_t st_uid;

gid_t st_gid;

off_t st_size;

};


/*server.c服务器程序*/

#include

#include

#include

#include

#include


int main(int argc,char **argv)

{

int shm_id;

char *ptr;

sem_t *sem;


if(argc!=2)

{

printf(“usage:shm_open \n”);

exit(1);

}

shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存区*/

ftruncate(shm_id,100);/*调整共享内存区大小*/

sem=sem_open(argv[1],O_CREAD,0644,1);/*创建信号量*/

ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/

strcpy(ptr,”\<0”>);

while(1)

{

if((strcmp(ptr,”\<0”>))==0)/*如果为空,则等待*/

continue;

else

{

if((strcmp(ptr,”q\n”))==0)/*如果内存为q\n退出循环*/

break;

sem_wait(sem);/*申请信号量*/

printf(“server:%s”,ptr);/*输入共享内存区内容*/

strcpy(ptr,”\<0”>);/*清空共享内存区*/

sem_pose(sem);/*释放信号量*/

}

sem_unlink(argv[1]);/*删除信号量*/

shm_unlink(argv[1]);/*删除共享内存区*/

}

}



/*server.c服务器程序*/

#include

#include

#include

#include

#include

#include


int main(int argc,char **argv)

{

int shm_id;

char *ptr;

sem_t *sem;


if(argc!=2)

{

printf(“usage:shm_open \n”);

exit(1);

}

shm_id=shm_open(argv[1],0);/*打开共享内存区

sem=sem_open(argv[1],0);/*打开信号量*/

ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/

while(1)

{

sem_wait(sem);/*申请信号量*/

fgets(ptr,10,stdin);/*从键盘读入数据到共享内存区*/

printf(“user:%s”,ptr);

if((strcmp(ptr,”q\n”))==0)

exit(0);

sem_pose(sem);/*释放信号量*/

sleep(1);

}

exit(0);

}




附录三 消息队列

消息队列主要用于:

  1. 不同进程、线程间同步
  2. 不同进程、线程间传递数据或者命令


头文件:#include


mqd_t mq_open (const char *name, int oflag, int mode_t, mq_attr* attr)


mq_attr structure have the following fields:
long    mq_flags    Message queue flags.

long    mq_maxmsg   Maximum number of messages.

long    mq_msgsize  Maximum message size.

long    mq_curmsgs  Number of messages currently queued.


return a message queue descriptor; otherwise, the function shall return (mqd_t)-1 and set errno to indicate the error. mqd_t is finally defined as int。

mq_open(pname, O_RDWR|O_CREAT|O_EXCL, 0666, NULL); pname必须是 “/filename“ 格式的。

int      mq_close(mqd_t id);

int      mq_unlink(const char *); 


int      mq_setattr(mqd_t id, struct mq_attr *newattr, struct mq_attr *oldattr);

int      mq_getattr(mqd_t id, struct mq_attr *attr);

setattr只能对mq_flags域起作用


ssize_t  mq_receive(mqd_t, char *, size_t, unsigned *);

ssize_t  mq_timedreceive(mqd_t, char *restrict, size_t, unsigned *, struct timespec *);
成功则返回接收到的消息长度

int      mq_send(mqd_t, const char *, size_t, unsigned );
int      mq_timedsend(mqd_t, const char *, size_t, unsigned , const struct timespec *);
int      mq_notify(mqd_t, const struct sigevent *);



附录四 进程函数

进程创建

fork

对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。


exec( )函数族

使 用exec函数族可以在一个进程启动另一个程序的执行。比如系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名 (filename)、参数列表(argv)以及环境变量(envp)。exec函数族当然不止一个,但它们大致相同,在 Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp,通过manexec命令可以了解它们的具 体情况。

一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序 分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继 承环境变量之类的信息。)


进程函数大全(未整理)

atexit(设置程序正常结束前调用的函数)

相关函数 _exit,exit,on_exit

表头文件 #include

定义函数 int atexit (void (*function)(void));

函数说明 atexit()用来设置一个程序正常结束前调用的函数。当程序通过调用exit()

或从main中返回时,参数function所指定的函数会先被调用,然后才真正由exit()结束

程序。

返回值 如果执行成功则返回0,否则返回-1,失败原因存于errno中。

范例 #include

void my_exit(void)

{

printf(“before exit () !\n”);

}

main()

{

atexit (my_exit);

exit(0);

}

执行 before exit()!



execl(执行文件)

相关函数 fork,execle,execlp,execv,execve,execvp

表头文件 #include

定义函数 int execl(const char * path,const char * arg,....);

函数说明 execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行

该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结

束。

返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno

中。

范例 #include

main()

{

execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);

}

执行 /*执行/bin/ls -al /etc/passwd */

-rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd



execlp(从PATH 环境变量中查找文件并执行)

相关函数 fork,execl,execle,execv,execve,execvp

表头文件 #include

定义函数 int execlp(const char * file,const char * arg,……);

函数说明 execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到

后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后

一个参数必须用空指针(NULL)作结束。

返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno

中。

错误代码 参考execve()。

范例 /* 执行ls -al /etc/passwd execlp()会依PATH 变量中的/bin找到/bin/ls */

#include

main()

{

execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char *)0);

}

执行 -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd



execv(执行文件)

相关函数 fork,execl,execle,execlp,execve,execvp

表头文件 #include

定义函数 int execv (const char * path, char * const argv[ ]);

函数说明 execv()用来执行参数path字符串所代表的文件路径,与execl()不同的地方

在于execve()只需两个参数,第二个参数利用数组指针来传递给执行文件。

返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno

中。

错误代码 请参考execve()。

范例 /* 执行/bin/ls -al /etc/passwd */

#include

main()

{

char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char*) }};

execv(“/bin/ls”,argv);

}

执行 -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd



execve(执行文件)

相关函数 fork,execl,execle,execlp,execv,execvp

表头文件 #include

定义函数 int execve(const char * filename,char * const argv[ ],char * const

envp[ ]);

函数说明 execve()用来执行参数filename字符串所代表的文件路径,第二个参数系利

用数组指针来传递给执行文件,最后一个参数则为传递给执行文件的新环境变量数组。

返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno

中。

错误代码 EACCES

1. 欲执行的文件不具有用户可执行的权限。

2. 欲执行的文件所属的文件系统是以noexec 方式挂上。

3.欲执行的文件或script翻译器非一般文件。

EPERM

1.进程处于被追踪模式,执行者并不具有root权限,欲执行的文件具有SUID 或SGID

位。

2.欲执行的文件所属的文件系统是以nosuid方式挂上,欲执行的文件具有SUID 或SGID

位元,但执行者并不具有root权限。

E2BIG 参数数组过大

ENOEXEC 无法判断欲执行文件的执行文件格式,有可能是格式错误或无法在此平台执

行。

EFAULT 参数filename所指的字符串地址超出可存取空间范围。

ENAMETOOLONG 参数filename所指的字符串太长。

ENOENT 参数filename字符串所指定的文件不存在。

ENOMEM 核心内存不足

ENOTDIR 参数filename字符串所包含的目录路径并非有效目录

EACCES 参数filename字符串所包含的目录路径无法存取,权限不足

ELOOP 过多的符号连接

ETXTBUSY 欲执行的文件已被其他进程打开而且正把数据写入该文件中

EIO I/O 存取错误

ENFILE 已达到系统所允许的打开文件总数。

EMFILE 已达到系统所允许单一进程所能打开的文件总数。

EINVAL 欲执行文件的ELF执行格式不只一个PT_INTERP节区

EISDIR ELF翻译器为一目录

ELIBBAD ELF翻译器有问题。

范例 #include

main()

{

char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char *)0};

char * envp[ ]={“PATH=/bin”,0}

execve(“/bin/ls”,argv,envp);

}

执行 -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd



execvp(执行文件)

相关函数 fork,execl,execle,execlp,execv,execve

表头文件 #include

定义函数 int execvp(const char *file ,char * const argv []);

函数说明 execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找

到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。

返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno

中。

错误代码 请参考execve()。

范例 /*请与execlp()范例对照*/

#include

main()

{

char * argv[ ] ={ “ls”,”-al”,”/etc/passwd”,0};

execvp(“ls”,argv);

}

执行 -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd



exit(正常结束进程)

相关函数 _exit,atexit,on_exit

表头文件 #include

定义函数 void exit(int status);

函数说明 exit()用来正常终结目前进程的执行,并把参数status返回给父进程,而进

程所有的缓冲区数据会自动写回并关闭未关闭的文件。

返回值

范例 参考wait()


 

_exit(结束进程执行)

相关函数 exit,wait,abort

表头文件 #include

定义函数 void _exit(int status);

函数说明 _exit()用来立刻结束目前进程的执行,并把参数status返回给父进程,并关

闭未关闭的文件。此函数调用后不会返回,并且会传递SIGCHLD信号给父进程,父进程可

以由wait函数取得子进程结束状态。

返回值


附加说明 _exit()不会处理标准I/O 缓冲区,如要更新缓冲区请使用exit()。



vfork(建立一个新的进程)

相关函数 wait,execve

表头文件 #include

定义函数 pid_t vfork(void);

函数说明 vfork()会产生一个新的子进程,其子进程会复制父进程的数据与堆栈空间,

并继承父进程的用户代码,组代码,环境变量、已打开的文件代码、工作目录和资源限

制等。Linux 使用copy-on-write(COW)技术,只有当其中一进程试图修改欲复制的空间

时才会做真正的复制动作,由于这些继承的信息是复制而来,并非指相同的内存空间,

因此子进程对这些变量的修改和父进程并不会同步。此外,子进程不会继承父进程的文

件锁定和未处理的信号。注意,Linux不保证子进程会比父进程先执行或晚执行,因此编

写程序时要留意

死锁或竞争条件的发生。

返回值 如果vfork()成功则在父进程会返回新建立的子进程代码(PID),而在新建立的

子进程中则返回0。如果vfork 失败则直接返回-1,失败原因存于errno中。

错误代码 EAGAIN 内存不足。ENOMEM 内存不足,无法配置核心所需的数据结构空间。

范例 #include

main()

{

if(vfork() = =0)

{

printf(“This is the child process\n”);

}else{

printf(“This is the parent process\n”);

}

}

执行 this is the parent process

this is the child process



getpgid(取得进程组识别码)

相关函数 setpgid,setpgrp,getpgrp

表头文件 #include

定义函数 pid_t getpgid( pid_t pid);

函数说明 getpgid()用来取得参数pid 指定进程所属的组识别码。如果参数pid为0,则

会取得目前进程的组识别码。

返回值 执行成功则返回组识别码,如果有错误则返回-1,错误原因存于errno中。

错误代码 ESRCH 找不到符合参数pid 指定的进程。

范例 /*取得init 进程(pid=1)的组识别码*/

#include

mian()

{

printf(“init gid = %d\n”,getpgid(1));

}

执行 init gid = 0



getpgrp(取得进程组识别码)

相关函数 setpgid,getpgid,getpgrp

表头文件 #include

定义函数 pid_t getpgrp(void);

函数说明 getpgrp()用来取得目前进程所属的组识别码。此函数相当于调用getpgid

(0);

返回值 返回目前进程所属的组识别码。

范例 #include

main()

{

printf(“my gid =%d\n”,getpgrp());

}


执行 my gid =29546



getpid(取得进程识别码)

相关函数 fork,kill,getpid

表头文件 #include

定义函数 pid_t getpid(void);

函数说明 getpid()用来取得目前进程的进程识别码,许多程序利用取到的此值来建

立临时文件,以避免临时文件相同带来的问题。

返回值 目前进程的进程识别码

范例 #include

main()

{

printf(“pid=%d\n”,getpid());

}


执行 pid=1494 /*每次执行结果都不一定相同*/



getppid(取得父进程的进程识别码)

相关函数 fork,kill,getpid

表头文件 #include

定义函数 pid_t getppid(void);

函数说明 getppid()用来取得目前进程的父进程识别码。

返回值 目前进程的父进程识别码。

范例 #include

main()

{

printf(“My parent ‘pid =%d\n”,getppid());

}

执行 My parent pid =463



getpriority(取得程序进程执行优先权)

相关函数 setpriority,nice

表头文件 #include

#include

定义函数 int getpriority(int which,int who);

函数说明 getpriority()可用来取得进程、进程组和用户的进程执行优先权。

参数 which有三种数值,参数who 则依which值有不同定义

which who 代表的意义

PRIO_PROCESS who 为进程识别码

PRIO_PGRP who 为进程的组识别码

PRIO_USER who 为用户识别码

此函数返回的数值介于-20 至20之间,代表进程执行优先权,数值越低代表有较高的优

先次序,执行会较频繁。

返回值 返回进程执行优先权,如有错误发生返回值则为-1 且错误原因存于errno。

附加说明 由于返回值有可能是-1,因此要同时检查errno是否存有错误原因。最好在调

用次函数前先清除errno变量。

错误代码 ESRCH 参数which或who 可能有错,而找不到符合的进程。EINVAL 参数

which 值错误。



nice(改变进程优先顺序)

相关函数 setpriority,getpriority

表头文件 #include

定义函数 int nice(int inc);

函数说明 nice()用来改变进程的进程执行优先顺序。参数inc数值越大则优先顺序排在

越后面,即表示进程执行会越慢。只有超级用户才能使用负的inc 值,代表优先顺序排

在前面,进程执行会较快。

返回值 如果执行成功则返回0,否则返回-1,失败原因存于errno中。

错误代码 EPERM 一般用户企图转用负的参数inc值改变进程优先顺序。



on_exit(设置程序正常结束前调用的函数)

相关函数 _exit,atexit,exit

表头文件 #include

定义函数 int on_exit(void (* function)(int, void*),void *arg);

函数说明 on_exit()用来设置一个程序正常结束前调用的函数。当程序通过调用exit()

或从main中返回时,参数function所指定的函数会先被调用,然后才真正由exit()结束

程序。参数arg指针会传给参数function函数,详细情况请见范例。

返回值 如果执行成功则返回0,否则返回-1,失败原因存于errno中。

附加说明

范例 #include

void my_exit(int status,void *arg)

{

printf(“before exit()!\n”);

printf(“exit (%d)\n”,status);

printf(“arg = %s\n”,(char*)arg);

}

main()

{

char * str=”test”;

on_exit(my_exit,(void *)str);

exit(1234);

}

执行 before exit()!

exit (1234)

arg = test



setpgid(设置进程组识别码)

相关函数 getpgid,setpgrp,getpgrp

表头文件 #include

定义函数 int setpgid(pid_t pid,pid_t pgid);

函数说明 setpgid()将参数pid 指定进程所属的组识别码设为参数pgid 指定的组识别

码。如果参数pid 为0,则会用来设置目前进程的组识别码,如果参数pgid为0,则会以

目前进程的进程识别码来取代。

返回值 执行成功则返回组识别码,如果有错误则返回-1,错误原因存于errno中。

错误代码 EINVAL 参数pgid小于0。

EPERM 进程权限不足,无法完成调用。

ESRCH 找不到符合参数pid指定的进程。



setpgrp(设置进程组识别码)

相关函数 getpgid,setpgid,getpgrp

表头文件 #include

定义函数 int setpgrp(void);

函数说明 setpgrp()将目前进程所属的组识别码设为目前进程的进程识别码。此函数相

当于调用setpgid(0,0)。

返回值 执行成功则返回组识别码,如果有错误则返回-1,错误原因存于errno中。



setpriority(设置程序进程执行优先权)

相关函数 getpriority,nice

表头文件 #include

#include

定义函数 int setpriority(int which,int who, int prio);

函数说明 setpriority()可用来设置进程、进程组和用户的进程执行优先权。参数

which有三种数值,参数who 则依which值有不同定义

which who 代表的意义

PRIO_PROCESS who为进程识别码

PRIO_PGRP who 为进程的组识别码

PRIO_USER who为用户识别码

参数prio介于-20 至20 之间。代表进程执行优先权,数值越低代表有较高的优先次序,

执行会较频繁。此优先权默认是0,而只有超级用户(root)允许降低此值。

返回值 执行成功则返回0,如果有错误发生返回值则为-1,错误原因存于errno。

ESRCH 参数which或who 可能有错,而找不到符合的进程

EINVAL 参数which值错误。

EPERM 权限不够,无法完成设置

EACCES 一般用户无法降低优先权



system(执行shell 命令)

相关函数 fork,execve,waitpid,popen

表头文件 #include

定义函数 int system(const char * string);

函数说明 system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执

行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用

system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。

返回值 如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数

string为空指针(NULL),则返回非零值。如果system()调用成功则最后会返回执行shell

命令后的返回值,但是此返回值也有可能为system()调用/bin/sh失败所返回的127,因

此最好能再检查errno 来确认执行成功。

附加说明 在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境

变量,通过环境变量可能会造成系统安全的问题。

范例 #include

main()

{

system(“ls -al /etc/passwd /etc/shadow”);

}


执行 -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd

-r--------- 1 root root 572 Sep 2 15 :34 /etc/shadow



wait(等待子进程中断或结束)

相关函数 waitpid,fork

表头文件 #include

#include

定义函数 pid_t wait (int * status);

函数说明 wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在

调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束

状态值会由参数status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状

态值,则

参数 status可以设成NULL。子进程的结束状态值请参考waitpid()。

返回值 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因

存于errno中。

附加说明

范例 #include

#include

#include

#include

main()

{

pid_t pid;

int status,i;

if(fork()= =0){

printf(“This is the child process .pid =%d\n”,getpid());

exit(5);

}else{

sleep(1);

printf(“This is the parent process ,wait for child...\n”;

pid=wait(&status);

i=WEXITSTATUS(status);

printf(“child’s pid =%d .exit status=^d\n”,pid,i);

}

}

执行 This is the child process.pid=1501

This is the parent process .wait for child...

child’s pid =1501,exit status =5



waitpid(等待子进程中断或结束)

相关函数 wait,fork

表头文件 #include

#include

定义函数 pid_t waitpid(pid_t pid,int * status,int options);

函数说明 waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果

在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结

束状态值会由参数status返回,而子进程的进程识别码也会一快返回。如果不在意结束

状态值,则参数status可以设成NULL。参数pid为欲等待的子进程识别码,其他数值意义

如下:

pid<-1 等待进程组识别码为pid绝对值的任何子进程。

pid=-1 等待任何子进程,相当于wait()。

pid=0 等待进程组识别码与目前进程相同的任何子进程。

pid>0 等待任何子进程识别码为pid的子进程。

参数option可以为0 或下面的OR 组合

WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等待。

WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。

子进程的结束状态返回后存于status,底下有几个宏可判别结束情况

WIFEXITED(status)如果子进程正常结束则为非0值。

WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断

是否正常结束才能使用此宏。

WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真

WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判

断后才使用此宏。

WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用

WUNTRACED 时才会有此情况。

WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才

使用此宏。


返回值 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因

存于errno中。

范例 参考wait()。



fprintf(格式化输出数据至文件)

相关函数 printf,fscanf,vfprintf

表头文件 #include

定义函数 int fprintf(FILE * stream, const char * format,.......);

函数说明 fprintf()会根据参数format字符串来转换并格式化数据,然后将结果输出到

参数stream指定的文件中,直到出现字符串结束('\0')为止。

返回值 关于参数format字符串的格式请参考printf()。成功则返回实际输出的字符

数,失败则返回-1,错误原因存于errno中。

范例 #include

main()

{

int i = 150;

int j = -100;

double k = 3.14159;

fprintf(stdout,”%d %f %x \n”,j,k,i);

fprintf(stdout,”%2d %*d\n”,i,2,i);

}

执行 -100 3.141590 96

150 150



fscanf(格式化字符串输入)

相关函数 scanf,sscanf

表头文件 #include

定义函数 int fscanf(FILE * stream ,const char *format,....);


函数说明 fscanf()会自参数stream的文件流中读取字符串,再根据参数format字符串

来转换并格式化数据。格式转换形式请参考scanf()。转换后的结构存于对应的参数内。

返回值 成功则返回参数数目,失败则返回-1,错误原因存于errno中。

附加说明

范例 #include

main()

{

int i;

unsigned int j;

char s[5];

fscanf(stdin,”%d %x %5[a-z] %*s %f”,&i,&j,s,s);

printf(“%d %d %s \n”,i,j,s);

}

执行 10 0x1b aaaaaaaaa bbbbbbbbbb /*从键盘输入*/

10 27 aaaaa



printf(格式化输出数据)

相关函数 scanf,snprintf

表头文件 #include

定义函数 int printf(const char * format,.............);

函数说明 printf()会根据参数format字符串来转换并格式化数据,然后将结果写出到

标准输出设备,直到出现字符串结束('\0')为止。参数format字符串可包含下列三种字

符类型

1.一般文本,伴随直接输出。

2.ASCII控制字符,如\t、\n等。

3.格式转换字符。

格式转换为一个百分比符号(%)及其后的格式字符所组成。一般而言,每个%符号在其

后都必需有一printf()的参数与之相呼应(只有当%%转换字符出现时会直接输出%字

符),而欲输出的数据类型必须与其相对应的转换字符类型相同。

Printf()格式转换的一般形式如下

%(flags)(width)(.prec)type

以中括号括起来的参数为选择性参数,而%与type则是必要的。底下先介绍type的几种

形式

整数

%d 整数的参数会被转成一有符号的十进制数字

%u 整数的参数会被转成一无符号的十进制数字

%o 整数的参数会被转成一无符号的八进制数字

%x 整数的参数会被转成一无符号的十六进制数字,并以小写abcdef表示

%X 整数的参数会被转成一无符号的十六进制数字,并以大写ABCDEF表示浮点型数

%f double 型的参数会被转成十进制数字,并取到小数点以下六位,四舍五入。

%e double型的参数以指数形式打印,有一个数字会在小数点前,六位数字在小数点

后,而在指数部分会以小写的e来表示。

%E 与%e作用相同,唯一区别是指数部分将以大写的E 来表示。

%g double 型的参数会自动选择以%f 或%e 的格式来打印,其标准是根据欲打印的数

值及所设置的有效位数来决定。

%G 与%g 作用相同,唯一区别在以指数形态打印时会选择%E 格式。

字符及字符串

%c 整型数的参数会被转成unsigned char型打印出。

%s 指向字符串的参数会被逐字输出,直到出现NULL字符为止

%p 如果是参数是“void *”型指针则使用十六进制格式显示。

prec 有几种情况

1. 正整数的最小位数。

2.在浮点型数中代表小数位数

3.在%g 格式代表有效位数的最大值。

4.在%s格式代表字符串的最大长度。

5.若为×符号则代表下个参数值为最大长度。

width为参数的最小长度,若此栏并非数值,而是*符号,则表示以下一个参数当做参数

长度。

flags 有下列几种情况

#NAME?

+ 一般在打印负数时,printf()会加印一个负号,整数则不加任何负号。此旗标会使

得在打印正数前多一个正号(+)。

# 此旗标会根据其后转换字符的不同而有不同含义。当在类型为o 之前(如%#o),则

会在打印八进制数值前多印一个o。

而在类型为x 之前(%#x)则会在打印十六进制数前多印’0x’,在型态为e、E、f、g

或G 之前则会强迫数值打印小数点。在类型为g 或G之前时则同时保留小数点及小数位数

末尾的零。

0 当有指定参数时,无数字的参数将补上0。默认是关闭此旗标,所以一般会打印出空白

字符。

返回值 成功则返回实际输出的字符数,失败则返回-1,错误原因存于errno中。

范例 #include

main()

{

int i = 150;

int j = -100;

double k = 3.14159;

printf(“%d %f %x\n”,j,k,i);

printf(“%2d %*d\n”,i,2,i); /*参数2 会代入格式*中,而与%2d同意义*/

}

执行 -100 3.14159 96

150 150



sacnf(格式化字符串输入)

相关函数 fscanf,snprintf

表头文件 #include

定义函数 int scanf(const char * format,.......);

函数说明 scanf()会将输入的数据根据参数format字符串来转换并格式化数据。Scanf

()格式转换的一般形式如下

%[*][size][l][h]type

以中括号括起来的参数为选择性参数,而%与type则是必要的。

* 代表该对应的参数数据忽略不保存。

size 为允许参数输入的数据长度。

l 输入的数据数值以long int 或double型保存。

h 输入的数据数值以short int 型保存。

底下介绍type的几种形式

%d 输入的数据会被转成一有符号的十进制数字(int)。

%i 输入的数据会被转成一有符号的十进制数字,若输入数据以“0x”或“0X”开头代

表转换十六进制数字,若以“0”开头则转换八进制数字,其他情况代表十进制。

%0 输入的数据会被转换成一无符号的八进制数字。

%u 输入的数据会被转换成一无符号的正整数。

%x 输入的数据为无符号的十六进制数字,转换后存于unsigned int型变量。

%X 同%x

%f 输入的数据为有符号的浮点型数,转换后存于float型变量。

%e 同%f

%E 同%f

%g 同%f

%s 输入数据为以空格字符为终止的字符串。

%c 输入数据为单一字符。

[] 读取数据但只允许括号内的字符。如[a-z]。

[^] 读取数据但不允许中括号的^符号后的字符出现,如[^0-9].

返回值 成功则返回参数数目,失败则返回-1,错误原因存于errno中。

范例 #include

main()

{

int i;

unsigned int j;

char s[5];

scanf(“%d %x %5[a-z] %*s %f”,&i,&j,s,s);

printf(“%d %d %s\n”,i,j,s);

}

执行 10 0x1b aaaaaaaaaa bbbbbbbbbb

10 27 aaaaa



sprintf(格式化字符串复制)

相关函数 printf,sprintf

表头文件 #include

定义函数 int sprintf( char *str,const char * format,.........);

函数说明 sprintf()会根据参数format字符串来转换并格式化数据,然后将结果复制到

参数str所指的字符串数组,直到出现字符串结束(’\0’)为止。关于参数format字符串

的格式请参考printf()。

返回值 成功则返回参数str字符串长度,失败则返回-1,错误原因存于errno中。

附加说明 使用此函数得留意堆栈溢出,或改用snprintf()。

范例 #include

main()

{

char * a=”This is string A!”;

char buf[80];

sprintf(buf,”>>> %s<<<\n”,a);

printf(“%s”.buf);

}

执行 >>>This is string A!<<<



sscanf(格式化字符串输入)

相关函数 scanf,fscanf

表头文件 #include

定义函数 int sscanf (const char *str,const char * format,........);

函数说明 sscanf()会将参数str的字符串根据参数format字符串来转换并格式化数据。

格式转换形式请参考scanf()。转换后的结果存于对应的参数内。

返回值 成功则返回参数数目,失败则返回-1,错误原因存于errno中。

范例 #include

main()

{

int i;

unsigned int j;

char input[ ]=”10 0x1b aaaaaaaa bbbbbbbb”;

char s[5];

sscanf(input,”%d %x %5[a-z] %*s %f”,&i,&j,s,s);

printf(“%d %d %s\n”,i,j,s);

}

执行 10 27 aaaaa



vfprintf(格式化输出数据至文件)

相关函数 printf,fscanf,fprintf

表头文件 #include

#include

定义函数 int vfprintf(FILE *stream,const char * format,va_list ap);

函数说明 vfprintf()会根据参数format字符串来转换并格式化数据,然后将结果输出

到参数stream指定的文件中,直到出现字符串结束(’\0’)为止。关于参数format字符

串的格式请参考printf()。va_list用法请参考附录C或vprintf()范例。

返回值 成功则返回实际输出的字符数,失败则返回-1,错误原因存于errno中。

范例 参考fprintf()及vprintf()。



vfscanf(格式化字符串输入)

相关函数 scanf,sscanf,fscanf

表头文件 #include

定义函数 int vfscanf(FILE * stream,const char * format ,va_list ap);

函数说明 vfscanf()会自参数stream 的文件流中读取字符串,再根据参数format字符

串来转换并格式化数据。格式转换形式请参考scanf()。转换后的结果存于对应的参数

内。va_list用法请参考附录C 或vprintf()。

返回值 成功则返回参数数目,失败则返回-1,错误原因存于errno中。

范例 参考fscanf()及vprintf()。



vprintf(格式化输出数据)

相关函数 printf,vfprintf,vsprintf

表头文件 #include

#include

定义函数 int vprintf(const char * format,va_list ap);

函数说明 vprintf()作用和printf()相同,参数format格式也相同。va_list为不定个

数的参数列,用法及范例请参考附录C。

返回值 成功则返回实际输出的字符数,失败则返回-1,错误原因存于errno中。

范例 #include

#include

int my_printf( const char *format,……)

{

va_list ap;

int retval;

va_start(ap,format);

printf(“my_printf( ):”);

retval = vprintf(format,ap);

va_end(ap);

return retval;

}

main()

{

int i = 150,j = -100;

double k = 3.14159;

my_printf(“%d %f %x\n”,j,k,i);

my_printf(“%2d %*d\n”,i,2,i);

}

执行 my_printf() : -100 3.14159 96

my_printf() : 150 150


 

vscanf(格式化字符串输入)

相关函数 vsscanf,vfscanf

表头文件 #include

#include

定义函数 int vscanf( const char * format,va_list ap);

函数说明 vscanf()会将输入的数据根据参数format字符串来转换并格式化数据。格式

转换形式请参考scanf()。转换后的结果存于对应的参数内。va_list用法请参考附录C或

vprintf()范例。

返回值 成功则返回参数数目,失败则返回-1,错误原因存于errno中。


 

vsprintf(格式化字符串复制)

相关函数 vnsprintf,vprintf,snprintf

表头文件 #include

定义函数 int vsprintf( char * str,const char * format,va_list ap);

函数说明 vsprintf()会根据参数format字符串来转换并格式化数据,然后将结果复制

到参数str所指的字符串数组,直到出现字符串结束(’\0’)为止。关于参数format字符

串的格式请参考printf()。va_list用法请参考附录C或vprintf()范例。

返回值 成功则返回参数str字符串长度,失败则返回-1,错误原因存于errno中。


vsscanf(格式化字符串输入)

相关函数 vscanf,vfscanf

表头文件 #include

定义函数 int vsscanf(const char * str,const char * format,va_list ap);

函数说明 vsscanf()会将参数str的字符串根据参数format字符串来转换并格式化数

据。格式转换形式请参考附录C 或vprintf()范例。

返回值 成功则返回参数数目,失败则返回-1,错误原因存于errno中。


附录五 参考资料

  1. 《Linux 程序设计》第三版 人民邮电出版社 Neil Matthew,Richard Stones 著 陈健等译

  2. 网络文档:http://blog.chinaunix.net/u/22935/article_52710.html

  3. 网络文档:







阅读(4074) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~