1.输入:一个文本文件,第一行是一个数字n,其他每行分别是一个单词以及它出现的百分比。
输出:输出n个单词,每个单词出现的几率符合它的百分比。
awk ‘BEGIN{sum=0;}1{n=$0}2,${up=sum+$1;while(sum< $1){arr[sum]+=$2;sum++;}if(i>100){exit 1;}}END{while(–n>0){print arr[rand()%100];}’
Continue reading
1.输入:一个文本文件,第一行是一个数字n,其他每行分别是一个单词以及它出现的百分比。
输出:输出n个单词,每个单词出现的几率符合它的百分比。
awk ‘BEGIN{sum=0;}1{n=$0}2,${up=sum+$1;while(sum< $1){arr[sum]+=$2;sum++;}if(i>100){exit 1;}}END{while(–n>0){print arr[rand()%100];}’
Continue reading →
(1).每个进程都属于一个进程组。进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号。每个进程组都有唯一的进程组ID(整数,也可以存放在pid_t类型中)。返回值是调用进程的进程组ID
1 2 |
#include <unistd.h> pid_t getpgrp(void); |
每个进程组都有一个组长进程,组长进程的标识是进程组ID等于其进程ID。组长进程可以创建一个进程组、创建该组中的进程。只有某个进程中有一个进程存在,则该进程就存在,与组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间区间成为进程组的生存期。进程组中最后一个进程可以终止或者转移到另一个进程组中。
Continue reading →
需求:某个目录下有若干日志文件,文件名是log.2011mmddhhmmss(mmddhhmmss为任意的时间戳),更改成log.2012mmddhhmmss。
这里必然用到正则表达式,首先想到一个Perl的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/perl if ($#ARGV != 0) { print "usage: command dirname"; exit(1); } my $basedir = $ARGV[0]; opendir(DIR, $basedir); @files = readdir(DIR); foreach (@files) { if ($_ =~ /log.2011d{10}/) { $newfile = $_; $newfile =~ s/(log.)(2011)(d*)/${1}2012$3/; rename $_, $newfile; } } |
用bash实现关键用for i in ls
来获得各个文件名,然后用管道和sed。
1 2 3 4 5 6 |
#!/usr/bin/bash cd $1; for file in `ls`; do mv $file `echo $file|sed 's/(log).2011(.*)/1.20122/'` done; |
当从一个fd读,写到另一个fd时,可以在下列形式的循环中使用阻塞I/0。
1 2 3 |
while((n = read(STDIN_FILENO, buf, BUFSIZ)) > 0) if (write(STDOUT_FILENO, buf, n) != n) exit(1); |
但是如果必须从两个fd中读,如果仍然使用阻塞式I/O,那么程序就会长时间阻塞在一个描述符上。这在网络编程中需要多个socket中获取数据的情况尤为常见。
解决方法一般有如下几种:
a).使用多进程/线程模型,每个进程/线程阻塞式等待一个fd。但是需要之间的多个信号通信机制,增加了程序的复杂性。
b).使用非阻塞式I/O(open with O_NOBLOCK),不断轮询(polling)多个描述符。但浪费CPU时间,并且多次执行read的系统调用。每次polling一遍后应该sleep若干时间,但这个时间很难确定。
c).使用信号驱动I/O模型。首先用sigaction设置SIGIO的信号处理程序,这样内核在数据ready的时候就发送一个SIGIO给进程,进程用信号处理程序接收并处理,完成时成功返回。
d).使用异步I/O(asynchronous I/O)。基本思想是进程告诉内核,当一个fd已经ready的时候,用一个signal通知它。需要注意的是,并非所有的UNIX系统都支持。(System V为这种机制提供了SIGPOLL信号,但是仅当fd是STEAMS设备的时才可用。另外这个信号对每个进程而言只有一个,如果该信号对两个fd都起作用则无法判断哪一个已经ready。为了确定,则将多个fd都设为非阻塞的,以此read来判断)。Linux支持异步I/O但是不默认支持STREAMS机制。与信号驱动I/O相比,信号驱动是通知发起时通知进程,然后将数据从内核读到进程空间。而异步I/O是完成全部过程才通知进程。
e).使用I/O复用(I/O multiplexing)。先构造一张有关fd的列表,然后调用一个函数。直到fd中一个已经准备进行I/O时,这个函数才返回。多路转接是这种问题实现的最好方式。具体函数介绍如下。
select函数使我们可以执行I/0多路转接,传向select的参数告诉内核:
(1).关心的fd
(2).对于每个fd关心的状态。(读,写或者异常)
从select返回,内核告诉我们:
(1).已经准备号的fd数量。
(2).对于读,写或者异常这三个状态中的每一个,哪些描述符已经准备好。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <sys/select.h> int select( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout ); /*返回值 *-1 出错 *0 没有描述符准备好,并超时 *>0 返回已准备好的描述符的数量,该值是三个描述符中已准备好的描述符之和,若一个文件描述符既准备好读,又准备好了写,那么返回2。 */ |
该函数提供了一种在单个进程中监视多个文件描述符的方法。可以对三种类型的描述符集进行监视:可读(第2个参数:readfds)、可写(第3个参 数:writefds)、处于异常状态(第4个参数:exceptfds)的描述符。从第2个参数起,参数都可以为空(NULL),当文件描述符集为空时,表示不监视其描述符的状态;nfds 是三个文件描述符号中最大的描述符+1。这样就会在一定的范围内搜索需要检测的描述符,否则,将会在所有可选的fd_set中搜索。
最后一个描述符为愿意等待的时间,
1 2 3 4 |
struct timeval { long tv_sec; /*seconds*/ long tv_usec; /*and microseconds*/ } |
timeval *timeout有三种情况
a). timeout == NULL 表示永远阻塞,直到fd准备好。
b). timeout->tv_sec == 0 && timeout->tv_usec == 0 表示完全不等待,测试所有的fd并立即返回。这样得到多个fd的状态而不阻塞select函数的polling方法。
c). timeout->tv_sec != 0 && timeout->tv_usec != 0 等待指定的秒数和毫秒数。当指定的fd之一已经ready时,或者指定时间到达时立即返回。如果是超时时返回则返回0。
Reference:
APUE Chapter 14
UNP Chapter 6
我们在开发(或使用)一个程序时,最怕的就是程序莫明其妙地当掉。虽然系统没事,但我们下次仍可能遇到相同的问题。于是这时操作系统就会把程序当掉 时的内存内容 dump 出来(现在通常是写在一个叫 core的file里面),让 我们或是debugger 做为参考。这个动作就叫作 core dump。
在 C/C++语言中,最常发生错误的地方就是指 针有问题。您可以利用 core 文件和debugger工具(gdb)把错误找出来。除了SIGSEGV(无效内存访问)信号以外,SIGABRT(异常终止), SIGBUS(硬件故障),SIGEMT(硬件故障),SIGFPE(算术异常),SIGILL(非法硬件指令),SIGIOT(硬件故障),SIGQUIT,SIGSYS(无效系统调用),SIGTRAP(硬件故障)等。
Continue reading →
守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统中大多数服务和系统任务都是通过守护进程实现的。例如,作业规划进程crond、打印进程lqd、nfsd、lockd、rpciod(这三个是NFS的守护进程)、portmap(端口映射守护进程提供将RPC程序号映射为port好的服务)、bdflush(当内存达到下限时,将buffer cache flush到磁盘上)、kupdated(每隔一段时间,kupdated将dirty page flush到磁盘上以免数据丢失)、syslogd(由帮助操作人员把系统消息记入日志的任何程序使用)、crond(指定的日期和时间执行命令)。
(1).创建子进程,父进程退出,这样让init收养子进程。
(2).调用setsid子进程中创建新会话
会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始与用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。
Continue reading →
pthread线程和信号
所有的异步信号发到整个进程的所有线程(异步信号如kill, lwp_kill, sigsend, kill等调用产生的都是,异步信号也称为中断),而且所有线程共享信号的处理行为(即sigaction的设置,对于同一信号的设置,某一线程的更改会影响到所有线程)。但每个线程可以有自己的mask来阻止信号的发送,所以可以通过线程对mask的设置来决定信号发送到哪个线程。
Continue reading →
线程私有数据是存储和查询与某个线程相关的数据的一种机制。把这种数据称为线程私有/特定数据的原因是希望每个线程可以独立地访问数据副本而无需担心数据同步问题。
设计线程私有数据接口的原因是:(1).线程ID不能保证是小而连续的整数。例如上一章的程序threadid得到的结果:
main thread: pid 9508 tid 1611388704 (0x600bd720)
new thread: pid 9508 tid 1603331840 (0x5f90e700)
Continue reading →
1.互斥量
当多个控制线程共享相同的内存时,需要确保每一个线程看到一致数据视图。如果不存在读取数据或者所有数据只读时不会存在一致性问题。如果某个线程正在修改变量而其他线程也可以读取或者修改这个变量的时候就需要对这些线程进行同步。在变量修改多于一个存储器访问周期时可能出现不一致的错误。(例如需要写的十一个长类型,需要多次写入内存。而写之间如果有其他线程读取就会出现同时读取一些修改过和没修改过的数据)。
通过使用pthread的互斥接口保护数据,确保同一时间只有一个线程访问数据。互斥量mutex本质上是一把锁,在访问共享资源前对互斥量加锁,然后其他任何试图访问互斥量加锁的线程将会被阻塞。访问完成后释放互斥量上的锁,被阻塞的线程将会变成可运行状态。同理,第一个变为可运行状态的进程将会锁住信号量,这样后面要访问该互斥量线程会被继续阻塞,直到回去再次等待它重新变为可用。
互斥量用pthread_mutex_t数据类型表示,在使用互斥量以前,必须首先对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量)或者使用pthread_mutex_init函数进行初始化。如果动态分配(malloc)那么释放内存前需要调用(pthread_mutex_destroy)
1 2 |
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destory(pthread_mutex_t *mutex); |
线程是在进程的基础上进一步的抽象,也就是说一个进程分为两个部分:线程集合和资源集合。线程是进程中的一个动态对象,它应该是一组独立的指令流,进程中的所有线程将共享进程里的资源。但是线程应该有自己的私有对象:比如程序计数器、堆栈和寄存器上下文。
线程分为三种类型:
(1).内核线程:
它的创建和撤消是由内核的内部需求来决定的,用来负责执行一个指定的函数,一个内核线程不需要和一个用户进程联系起来。它共享内核的正文段核全局数据,具有自己的内核堆栈。它能够单独的被调度并且使用标准的内核同步机制,可以被单独的分配到一个处理器上运行。内核线程的调度由于不需要经过态的转换并进行地址空间的重新映射,因此在内核线程间做上下文切换比在进程间做上下文切换快得多。
(2).轻量级进程:
轻量级进程是内核支持的用户线程,它在一个单独的进程中提供多线程控制。这些轻量级进程被单独的调度,可以在多个处理器上运行,每一个轻量级进程都被绑定在一个内核线程上,而且在它的生命周期这种绑定都是有效的。轻量级进程被独立调度并且共享地址空间和进程中的其它资源,但是每个LWP都应该有自己的程序计数器、寄存器集合、核心栈和用户栈。
(3).用户线程:
用户线程是通过线程库实现的。它们可以在没有内核参与下创建、释放和管理。线程库提供了同步和调度的方法。这样进程可以使用大量的线程而不消耗内核资源,而且省去大量的系统开销。用户线程的实现是可能的,因为用户线程的上下文可以在没有内核干预的情况下保存和恢复。每个用户线程都可以有自己的用户堆栈,一块用来保存用户级寄存器上下文以及如信号屏蔽等状态信息的内存区。库通过保存当前线程的堆栈和寄存器内容载入新调度线程的那些内容来实现用户线程之间的调度和上下文切换。
内核仍然负责进程的切换,因为只有内核具有修改内存管理寄存器的权力。用户线程不是真正的调度实体,内核对它们一无所知,而只是调度用户线程下的进程或者轻量级进程,这些进程再通过线程库函数来调度它们的线程。当一个进程被抢占时,它的所有用户线程都被抢占,当一个用户线程被阻塞时,它会阻塞下面的轻量级进程,如果进程只有一个轻量级进程,则它的所有用户线程都会被阻塞。
Linux线程是通过进程来实现。Linux kernel为进程创建提供一个clone()系统调用,clone的参数包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等。通过clone()的参数,新创建的进程,也称为LWP(Lightweight process)与父进程共享内存空间,文件句柄,信号处理等,从而达到创建线程相同的目的。
在Linux 2.6之前,Linux kernel并没有真正的thread支持,一些thread library都是在clone()基础上的一些基于user space的封装,因此通常在信号处理、进程调度(每个进程需要一个额外的调度线程)及多线程之间同步共享资源等方面存在一定问题。Linux 2.6的线程库叫NPTL(Native POSIX Thread Library)。POSIX thread(pthread)是一个编程规范,通过此规范开发的多线程程序具有良好的跨平台特性。尽管是基于进程的实现,但新版的NPTL创建线程的效率非常高。一些测试显示,基于NPTL的内核创建10万个线程只需要2秒,而没有NPTL支持的内核则需要长达15分钟。
在Linux中,每一个线程都有一个task_struct。线程和进程可以使用同一调度其调度。内核角度上来将LWP和Process没有区别,有的仅仅是资源的共享。如果独享资源则是HWP,共享资源则是LWP。而在真正内核实现的
NPTL的实现是在kernel增加了futex(fast userspace mutex)支持用于处理线程之间的sleep与wake。futex是一种高效的对共享资源互斥访问的算法。kernel在里面起仲裁作用,但通常都由进程自行完成。NPTL是一个1×1的线程模型,即一个线程对于一个操作系统的调度进程,优点是非常简单。而其他一些操作系统比如Solaris则是MxN的,M对应创建的线程数,N对应操作系统可以运行的实体。(N (1). 测试宏 (3).线程创建 //若成功则返回0,否则返回错误编号。它不设置errno void pthread_exit(void *rval_ptr); int pthread_join(pthread_t thread, void **rval_ptr); pthread_cleanup_pop删除上一次注册的线程清理处理程序。如果execute非0,则执行这个程序。 参考:
#ifdef _POSIX_THREADS
或者
sysconf(_SC_THREADS)
来检测是否支持宏。
(2). 线程标识pthread_t
pit_t是整个系统中唯一的。而线程pthread_t(struct)不同,只在它所属的进程环境有效。
判断两个线程ID是否相等:
程序启动时,它是以单进程的单个控制进程启动的。如果要增加新的线程,可以调用pthread_create来创建。
C99中的新关键字restrict以及受限指针(restricted pointers)。受限指针作为一种编译器优化代码的方式,是由编译器厂商提供特定的实现.
restrict只可以用在指针身上。如果一个指针被restrict修饰,那么就在它(这个指针)和它所指向的对象之间建立了一种特殊的联系──只能用这个指针或者这个指针的表达式来访问这个对象的值.一个指针指向一个内存地址。同一块内存可以由多个指针来访问并在程序运行时修改它(这块内存)。restrict告诉编译器,如果一块由一个被restrict修饰的指针所指向的内存被修改了,那么没有其它的指针可以来访问这块内存。编译器可能会选择一种方式来优化代码中调用被restrict修饰的指针的部分,这可能导致错误发生。程序员有责任来确保正确地按照他们所设想的来使用被restrict修饰的指针,否则的话,可能会发生意想不到的结果。
如果一块特定的内存区没有被修改,那么它可以被多个restrict指针(被restrict修饰的指针)所指代(或者叫做引用或访问)。另外,restrict指针的赋值是有限制的,这一点在函数调用和嵌套块(nested block)之间是没有区别的。在包含restrict指针的块中,只能将外层的restrict指针的值赋给内层的restrict指针,在同层内不可以相互赋值,当然在外层不可能赋以内层的值。一个例外是,当一个声明restrict指针的代码快执行完后,这个restrict指针所指的内存就可以被其它的指针访问了(因为那个restrict是那个代码块的局部变量,当执行完那个代码块后,这个指针也就不复存在了)。
(4). 线程终止
如果进程中的任一线程调用了exit, __EXIT虎或_exit,那么整个进程就会终止。与之类似如果信号的默认动作是终止进程,那么信号发送到线程就会终止整个进程。
线程的退出方式:
(1).线程只从启动函数中返回,返回值是线程的退出码。
(2).线程可以被同一进程中其他线程取消。
(3).线程调用pthread_exit()。
rval_ptr是一个无类型指针,与传给启动函数的单个参数类似。进程中其他进程可以调用pthread_join函数访问到这个指针。如果线程从启动函数返回,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。如果对线程的返回值不感兴趣,那么就可以把rval_ptr设置为NULL。在样调用pthread_join函数将等待指定的线程终止,但并不获取线程的终止状态。例如在线程启动函数中返回:return ((void *) 1)这样pthread_join的函数可以通过rval_ptr这个指针得到线程的退出状态。如果线程调用ptread_exit((void *2)),那么pthread_join依然可以得到退出状态2.这个指针可以是复杂的结构类型以便保存更多信息。
/* 成功返回0,否则返回错误编号 */
调用pthread_join的线程将被阻塞,直到指定的线程(thread)调用pthread_exit,从启动函数或者被取消。另外,void *rval_ptr可以作为一个int直接返回。例如return (void *)1;或者pthread_exit((void *)1);那么从pthread_join获得该指针直接可以用(int)ptr来输出。
这个void *rval_ptr指向的内存地址不应该在栈上,其内容会被下面调用的函数覆盖。应该是在堆上(通过malloc来创建)或者在静态数据段(用静态,全局变量)。
int pthread_cancel(pthread_t tid);
/* 成功返回0,否则返回错误编号 */
行为相等于参数值等同于PTHREAD_CANCELED的pthread_exit函数。
注意:线程可以自行选择忽略取消或者控制取消方式。注意pthread_cancel并不等待线程终止,仅仅是提出请求。
线程可以自己安排退出时需要执行的函数,这与进程atexit函数安排退出时要执行的函数是类似的。这样的函数称为线程清理处理程序。线程可以建立多个线程处理函数,存放在栈中,执行顺序与他们注册的顺序相反。
关于参数的使用方法的一个例子:
APUE
ULK
fork vfork clone pthread_create http://blog.csdn.net/sandflee/article/details/5189312