信号的基本概念:
信号可以看成是用户程序空间的软件中断, 提供了一种处理异步事件的方法; 每个信号都有个名字, 可用man 7 signal查看, 可用kill -l查看系统所支持的信号集, 常用的一些信号名称为:
SIGABRT 异常退出信号, 调用abort时产生;
SIGTERM kill命令发出的默认信号;
SIGALRM 闹钟信号, alarm设置的时间到达时产生;
SIGINT 中断信号, Ctrl-c是产生;
SIGCHLD 子进程终止时产生;
SIGSEGV 无效内存访问时产生;
SIGPIPE 管道的读进程已经终止后, 往此管道写数据时产生;
SIGKILL 使用kill 9 pid时产生的强行终止进程信号;
由终端产生信号:
终端的某些按键可以产生信号, 具体那个按键产生那种信号, stty -a可以查看;
可以要求系统在某个信号出现时执行下列三种操作之一:
1. 忽略此信号. 有两种信号不能被忽略, SIGKILL, SIGSTOP, 因为他们向超级用户提供了一种使进程终止的可靠方法;
2. 捕捉信号. 可通知内核在某种信号发生时调用一个用户函数.
3. 执行系统默认动作. 包括Term终止, Ign忽略, Core终止并coredump, Stop挂起, Cont使挂起进程继续; 不同的信号有不同的系统默认动作. 具体可查阅man 7 signal手册.
可重入函数: 一般指不对任何全局数据进行操作的函数代码; 符合下列条件之一的就是不可重入函数:
1. 使用了静态数据结构(全局或局部);
2. 调用了malloc或free;
3. 使用了标准I/O库.使用了全局数据结构.
事件发生时, 内核为进程产生一个信号generation, 当实际执行信号所对应动作时, 被称为向一个进程递送了一个信号delivery, 信号在产生和递送之间的状态, 称为信号未决pending.
如果产生的信号被阻塞, 则此信号保持在未决状态. 如果信号在被阻塞时发生了多次, linux有2种处理, 对常规信号递送之前多次只算一次, 实时信号的多次放在一个队列里依次等待递送.
每个进程有一个信号屏蔽字, 规定了当前要阻塞的信号集. 每种可能的信号, 信号屏蔽字中都有对应的bit, 其类型为sigset_t.
信号产生时, 内核依据下列条件来判断该信号是否可以被忽略:
被阻塞的, 不能忽略, 即使他的注册处理动作是忽略, 也不能被忽略.
未被阻塞的, 且处理动作为忽略的信号可以忽略不记录.
对于未被阻塞且不可忽略的信号, 当进程每次从内核态回到用户态之前处理这些信号(即递送该信号);
信号屏蔽与处理函数:
#include
int sigemptyset(sigset_t *set); 全不阻塞
int sigfillset(sigset_t *set); 全阻塞
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
返回值成功为0, 出错为-1;
int sigismember(const sigset_t *set, int signo); 判断信号集中是否包含了指定信号;
返回值若真为1, 若假为0;
一个进程的信号屏蔽字规定了当前阻塞的信号集, 调用sigprocmask可以检测或更改进程的信号屏蔽字;
#include
int sigpromask(int how, const sigset_t *set, sigset_t *oset);
返回值成功为0, 出错为-1;
oset为非空指针, 进程的当前屏蔽字通过oset返回.
若set是空指针, 则不改变当前的信号屏蔽字, 仅通过oset返回当前的信号字;
若set为非空指针, 则根据参数how来决定如何用set来修改当前的信号字; how有三种值:
SIG_BLOCK set包含了我们希望添加到信号屏蔽字中的要阻塞的信号
SIG_UNBLOCK set包含了我希望从当前屏蔽字中解除阻塞的信号
SIG_SETMASK 设置把set的值设为当前的信号屏蔽字
sigpending函数返回当前该进程当前未决的信号集.
#include
int sigpending(sigset_t *set); 返回成功为0, 出错为-1; 内容通过set参数返回;
sigaction功能是检查或修改与指定信号相关联的处理动作;
#include
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); 返回值成功为0, 出错为-1;
signo为信号编号;
若act非空, 则根据act修改该信号的处理动作.
若oact非空, 则系统将该信号原来的处理动作在此返回;
struct sigaction{
void (*sa_handler)(); //用户处理函数, 或SIG_IGN忽略, 或SIG_DFL系统默认
sigset_t sa_mask; //需要增加为阻塞的信号集. 调用捕捉函数之前该信号集被加进进程的信号屏蔽字, 从信号捕捉函数返回时再恢复原来值; 信号处理程序被调用时, 系统建立的新信号屏蔽字会自动包括正在被递送的信号.因此保证了在处理一个给定的信号时, 即使该信号再发生, 也会被阻塞到当前处理结束为止, 因而信号处理函数不必是可重入的.
int sa_flags; //包含了对信号处理的各种选项, 不常用. 一般设为0即可;
}
一旦对给定的信号设置了一个动作, 那么在sigaction改变它之前, 该设置一直有效;
挂起等待信号的函数:
#include
int pause(void); 返回-1, errno设置为EINTR; 使进程挂起直到捕捉到一个信号, 并从处理程序返回时, 才返回;
由于异步事件在任何时间都可能发生, 程序的执行往往不是我们所希望的那样, 这叫做竞态条件race condition, 也叫竞争-冒险;
需要一个原子操作中实现恢复信号屏蔽字然后使进程睡眠的功能, sigsuspend函数实现了这点;
#include
int sigsuspend(const sigset_t *sigmask); 返回-1, errno设为EINTR(表示一个被中断的系统调用);
进程的信号屏蔽字设置为sigmask(其中确保解除了阻塞)所指向的值; 返回时恢复到调用sigsuspend之前的值;
相当于三步的集成: 解除屏蔽, 即使用sigmask的值, pause阻塞在此, 恢复原值;
引发信号的函数:
#include
unsigned int alarm(unsigned int seconds); 返回0或之前设置的闹钟时间还余下的秒数;
时间超过后, 产生SIGALRM信号, 如果不忽略且不捕捉此信号, 默认动作是终止该进程;
每个进程只能有一个闹钟时间. 若调用alarm时, 已经设置过闹钟时间, 而且还没超时, 则该闹钟时间的余值作为返回值, 闹钟时间被新值取代; 如果有以前登记的尚未超时的闹钟时间, 而这次调用alarm(0), 则取消闹钟时间;
#include
void abort(void); 无返回值;
向调用它的进程发出一个SIGABRT信号, 使进程被异常终止, 也可自定义信号处理函数完成终止程序前的清理工作, 但处理函数中必须调用exit或_exit终止程序;
#include
int kill(pid_t pid, int signo); 向其他进程发送指定信号;
int raise(int signo); 向本进程自身发送信号;
返回值成功为0, 出错-1;
一些例程:
/*catch_alarm.c 捕捉信号应用*/
#include
#include
#include
#include
#include
#define PROMPT "Two seconds elapsed\n"
void prompt_info()
{
write(STDOUT_FILENO, PROMPT, strlen(PROMPT));
}
int main()
{
struct sigaction act;
act.sa_handler = prompt_info;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL);
alarm(2); /*here is a bug of race condition*/
while(1)
{
pause();
alarm(2);
}
return 0;
}
/*getsigchld.c 实验捕捉sigchld信号*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
void sig_chld(int signo)
{
printf ("---child proc cleared!---\n");
wait(NULL);
}
int main()
{
pid_t pid;
char *message;
int n, i;
int exit_code;
struct sigaction newact, oldact;
sigset_t newmask, oldmask, tmpmask;
sigemptyset(&tmpmask);
sigaddset(&tmpmask, SIGCHLD);
sigaddset(&tmpmask, SIGALRM);
sigprocmask(SIG_BLOCK, &tmpmask, &oldmask);
//sigdelset(&tmpmask, SIGCHLD);
//sigprocmask(SIG_UNBLOCK, &tmpmask, &oldmask); //right
sigprocmask(SIG_SETMASK, &oldmask, NULL); //right
for (i = 0; i < 31; i++)
{
printf("%d ", sigismember(&tmpmask, i));
}
puts("tmp");
for (i = 0; i < 31; i++)
{
printf("%d ", sigismember(&newmask, i));
}
puts("new");
newact.sa_handler = sig_chld;
newact.sa_flags = 0;
sigemptyset(&newact.sa_mask);
sigaction(SIGCHLD, &newact, &oldact);
printf("get sigchld prog starting\n");
pid = fork();
switch(pid)
{
case -1:
perror("fork failed");
exit(1);
case 0:
message = "This is the child";
n = 3;
exit_code = 37;
break;
default:
message = "This is the parent";
n = 1;
exit_code = 0;
pause();
break;
}
for (; n > 0; n--)
{
puts(message);
sleep(1);
}
}
/*my_sleep.c 实现一个sleep程序*/
#include
#include
#include
#include
static void sig_alrm(int signo)
{
/*nothing to do*/
}
unsigned int my_sleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact);
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(nsecs);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM);
sigsuspend(&suspmask);
unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return(unslept);
}
int main()
{
while(1)
{
printf ("2sec\n");
my_sleep(2);
}
}
关于线程:
在同一进程环境中使用多个线程可以共享所有的进程资源, 而且可以优化程序流程, 使很多工作异步进行;
线程单独拥有的资源有: 线程id, 堆栈, 信号屏蔽字, 一组寄存器的值, errno变量, 调度优先级和策略等;
线程共同拥有的资源有: 进程的所有资源包括程序代码, 全局变量, 堆栈空间, 文件描述符等;
POSIX.1-2001标准中规定的线程接口称为POSIX thread, 或pthreads. 编译时要加-lpthread.
目前的linux内核是以轻量级进程(lightweight process, LWP)的方式实现多线程的.
内核里每个LWP对应用户空间的一个线程, LWP拥有自己的task_struct, 也是一个进程调度单位;
LWP与普通进程的区别是多个LWP共享某些资源, 如: 地址空间, 打开的文件等;
Solaris的线程库就不是一个LWP对应一个用户空间线程, 而是用户空间分时复用数量有限的LWP.
线程控制:
#include
int pthread_create( pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
void *(*start_rtn)(void), void *restrict arg);
返回值成功为0, 出错返回错误号;
tidp 用于成功时返回tid值;
attr 表示线程的属性, 使用默认的线程属性可以赋NULL即可;
start_rtn 线程回调函数名称;
arg 给回调函数传参数, 一般是自定义的结构体, 可一次传多项数据;
新线程继承了原线程的上下文和信号屏蔽字, 但原线程的未决信号在新线程中被清除.
如果同一进程的任一线程调用了exit, _exit, 则整个进程的所有线程都终止. 因而, 如果一个信号的处理动作是终止进程, 这样的信号递送到任何一个线程, 整个进程的所有线程都终止;
如果要只终止线程, 三种方法:
1. 从线程函数返回, 返回值是线程的终止状态码, main函数这样处理会终止整个进程;
2. 调用pthread_cancel终止同一进程的另一线程;
3. 调用pthread_exit终止自己;
#include
void pthread_exit(void *rval_ptr);
rval_ptr 其类型可用户定义, 其空间必须是全局的或是malloc分配的, 不能在线程函数的栈上分配.
同一个进程的其他线程调用pthread_join可获得这个指针;
#include
int pthread_join(pthread_t thread, void **rval_ptr); 返回值成功为0, 出错返回错误号;
调用该函数的线程将挂起等待, 直到对应的线程通过上述三种方法之一返回;
rval_ptr指向的内存单元应由调用者事先分配, 如void * t_result; pthread_join(a_thread, &t_result);
三种返回方式对应的rval_ptr返回的内容是不同的:
1. 从线程函数返回, rval_ptr指向的单元存放的不是指针而是返回值;
2. 被令一线程pthread_cancel导致的返回, 存放的是常数PTHREAD_CANCELED;
3. 线程自己调用pthread_exit返回, 存放的就是pthread_exit(void *rval_ptr)里面的参数;
#include
int pthread_cancel(pthread_t tid); 返回值成功为0, 出错为错误号;
线程被其他线程cacel掉, 相当于自己调用pthread_exit(PTHREAD_CANCELED).
调用pthread_cancel的线程不必等到tid线程结束, 而只是发出这个请求.
tid线程也可以调整线程属性来忽略pthread_cancel请求.
#include
int pthread_detach(pthread_t tid); 成功返回0, 失败返回错误号;
线程终止后, 其终止状态一直保留到其他线程调用pthread_join来获取为止.
若线程被detach, 那么线程一旦终止, 资源就立刻被回收, 不保留其终止状态.
若线程已经被detach, 对它调用pthread_join会返回-EINVAL.
线程与进程函数的对比:
进程 线程
fork pthread_create
exit pthread_exit
waitpid pthread_join
abort pthread_cancel
线程间的同步:
多个线程访问同一共享资源时需要一些同步机制来保证共享数据的一致性, 与信号当中的可重入性问题相同;
临界区: 不允许被打断的区域;
读者写者锁reader-writer-lock, reader之间并不互斥, writer是独占的exclusive.
互斥锁mutex用pthread_mutex_t类型的变量表示.
如果静态分配该类型的变量, 可以使用常数PTHREAD_MUTEX_INITIALIZER初始化它 ?
如果调用malloc分配一个mutex, 则应该调用pthread_mutex_init()初始化它;
在调用free释放之前应该调用pthread_mutex_destroy()销毁它.
#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
成功返回0, 出错返回错误号;
attr是mutex的属性, 如果使用缺省属性初始化mutex, 传NULL即可;
#include
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); 尝试获取锁而不挂起等待, 失败返回-EBUZY.
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0, 出错返回错误号;
关于死锁deadlock, 出现的情形有:
1. 同一线程先后两次调用lock. 如: A ->A ->! A ->! A
2. 两个不同线程交叉嵌套用来多把锁. 如: 线程1: A ->B->! B->! A 线程2: B->A->! A->! B
线程的一些例程:
/*thread.c
*a simple thread program with args
*/
#include
#include
#define N 1
void *thread(void *arg)
{
printf("hello, world! \n", *(int*)arg);
sleep(2);
}
int main()
{
pthread_t tid[N];
int i, arg[N];
while(1)
{
sleep(1);
for (i = 0; i < N; i++)
{
arg[i] = i;
pthread_create(&tid[i], NULL, thread, (void*)&arg[i]);
}
}
/*
for (i = N-1; i >= 0; i--)
{
pthread_join(tid[i], NULL);
printf ("-----%d\n", i);
}*/
return 0;
}
/*threadmut.c
*a program to practice using mutex to lock,
*there is a common data:work_area[],
*which store the data input at main thread,
*and in the thread_function to use it.
*/
#include
#include
#include
#include
#include
#include
void *thread_function(void *arg);
pthread_mutex_t work_mutex;
#define WORK_SIZE 1024
char work_area[WORK_SIZE];
int time_to_exit = 0;
int main()
{
int res;
pthread_t a_thread;
void *thread_result;
res = pthread_mutex_init(&work_mutex, NULL);
if (res != 0)
{
perror ("Mutex initialization failed");
exit(EXIT_FAILURE);
}
res = pthread_create(&a_thread, NULL, thread_function, NULL);
if (res != 0)
{
perror("Thread creation failed");
exit (EXIT_FAILURE);
}
pthread_mutex_lock(&work_mutex);
printf("Input some text. Enter 'end' to finish\n");
while (!time_to_exit)
{
fgets(work_area, WORK_SIZE, stdin);
pthread_mutex_unlock(&work_mutex);
while(1)
{
pthread_mutex_lock(&work_mutex);
if (work_area[0] != '\0')
{
pthread_mutex_unlock(&work_mutex);
sleep(1);
}
else
{
break;
}
}
}
pthread_mutex_unlock(&work_mutex);
printf ("\nWaiting for thread to finish ...\n");
res = pthread_join(a_thread, &thread_result);
if (res != 0)
{
perror ("Thread join failed");
exit (EXIT_FAILURE);
}
printf ("Thread joined\n");
pthread_mutex_destroy(&work_mutex);
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg)
{
sleep(1);
pthread_mutex_lock(&work_mutex);
while(strncmp("end", work_area, 3) != 0)
{
printf ("Yor input %d characters\n",
strlen(work_area) - 1);
work_area[0] = '\0';
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
while (work_area[0] == '\0')
{
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
}
}
time_to_exit = 1;
work_area[0] = '\0';
pthread_mutex_unlock(&work_mutex);
pthread_exit(0);
}
/*queue.c
*a simple program to realize a queue
*/
#include
#include
#define N 16
int Q[N];
int front, rear;
void queue_init(int n)
{
front = rear = 0;
}
void queue_destroy()
{
front = rear = 0;
}
void enque(int item)
{
Q[rear++] = item;
rear %= N;
}
int deque()
{
int item = Q[front++];
front %= N;
return item;
}
int main()
{
int i;
queue_init(N);
for (i=0; i for (i=0; i puts("");
return 0;
}
/* Parallel C code to demonstrate Linux thread interface */
/* Algorithm appears to be based on the series expansion of arctan(pi/4) */
#include
#include
#include
volatile double pi = 0.0; /* Approximation to pi (shared) */
pthread_mutex_t pi_lock; /* Lock for above */
volatile double intervals; /* How many intervals? */
void *process(void *arg)
{
register double width, localsum;
register int i;
register int iproc = (*((char *) arg) - '0');
/* Set width */
width = 1.0 / intervals;
/* Do the local computations */
localsum = 0;
for (i=iproc; i {
register double x = (i + 0.5) * width;
localsum += 4.0 / (1.0 + x * x);
}
localsum *= width;
/* Lock pi for update, update it, and unlock */
pthread_mutex_lock(&pi_lock);
pi += localsum;
pthread_mutex_unlock(&pi_lock);
return(NULL);
}
int main(int argc, char **argv)
{
pthread_t thread0, thread1;
void * retval;
/* Get the number of intervals */
intervals = atoi(argv[1]);
/* Initialize the lock on pi */
pthread_mutex_init(&pi_lock, NULL);
/* Make the two threads */
pthread_create(&thread0, NULL, process, "0");
pthread_create(&thread1, NULL, process, "1");
/* Join (collapse) the two threads */
pthread_join(thread0, &retval);
pthread_join(thread1, &retval);
/* Print the result */
printf("Estimation of pi is %20.18f\n", pi);
return 0;
}
/*pd.c
*a program about philo eating
*/
#include
#include
#include
pthread_mutex_t chopstick[5];
void *philosopher(void *arg)
{
int p = *(int*)arg;
while (1) {
pthread_mutex_lock(&chopstick[p]);
pthread_mutex_lock(&chopstick[(p+1)%5]);
printf("philosopher %d eating...\n", p);
sleep(1);
pthread_mutex_unlock(&chopstick[p]);
pthread_mutex_unlock(&chopstick[(p+1)%5]);
printf("philosopher %d thinking...\n", p);
sleep(1);
}
}
int main()
{
int i, p[5];
pthread_t pid[5];
for (i=0; i<5; i++)
pthread_mutex_init(&chopstick[i], NULL);
for (i=0; i<5; i++) {
p[i] = i;
pthread_create(&pid[i], NULL,
philosopher, (void*)&p[i]);
}
for (i=0; i<5; i++)
pthread_join(pid[i], NULL);
return 0;
}
/*prodcom.c
*producer & consumer
*/
#include
#include
#include
sem_t empty, full; // the global semaphores
int data; // shared buffer
int num;
// deposit 1, ..., num into the data buffer
void *Producer(void *arg)
{
int produced;
printf("Producer created\n");
for (produced = 1; produced <= num; produced++) {
sem_wait(&empty);
data = produced;
sem_post(&full);
}
}
// fetch num items from the buffer and sum them
void *Consumer(void *arg)
{
int total = 0, consumed;
printf("Consumer created\n");
for (consumed = 0; consumed < num; consumed++) {
sem_wait(&full);
total = total+data;
sem_post(&empty);
}
printf("for %d iterations, the total is %d\n", num, total);
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
num = atoi(argv[1]);
sem_init(&empty, 0, 1); // sem empty = 1
sem_init(&full, 0, 0); // sem full = 0
printf("main started\n");
pthread_create(&pid, NULL, Producer, NULL);
pthread_create(&cid, NULL, Consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
printf("main done\n");
}
/*q_prodom.c
*a program about productor and cosummer
*/
#include
#include
#include
#include
#define N 8
#define PN 1
#define CN 2
sem_t empty, full;
int num;
int Q[N], front, rear;
void queue_init(int n) { front = rear = 0; }
void enque(int item)
{
printf("producer%5d\n", rear);
Q[rear++] = item;
rear %= N;
}
int deque()
{
printf("consumer%5d\n", front);
int item = Q[front++];
front %= N;
return item;
}
void *Producer(void *arg)
{
int p = *(int*)arg;
while(1){
sleep(1);
sem_wait(&empty);
printf("%d ", p);
enque(1);
sem_post(&full);
}
}
void *Consumer(void *arg)
{
int c = *(int*)arg;
while (1) {
// sleep(1);
sem_wait(&full);
printf("%d ", c);
deque();
sem_post(&empty);
}
}
int main(int argc, char *argv[])
{
int i, p[PN], c[CN];
pthread_t pid[PN], cid[CN];
sem_init(&empty, 0, N); // sem empty = 1
sem_init(&full, 0, 0); // sem full = 0
for(i=0; i p[i] = i;
pthread_create(
&pid[i], NULL, Producer, (void*)&p[i]);
}
for(i=0; i c[i] = i;
pthread_create(
&cid[i], NULL, Consumer, (void*)&c[i]);
}
for(i=0; i pthread_join(pid[i], NULL);
for(i=0; i pthread_join(cid[i], NULL);
return 0;
}
信息点滴:
ulimit -c 1024 允许core文件最大为1024K字节
kill -SIGSEGV pid 向进程发送一个SIGSEGV信号
alarm(sec) 发送SIGALRM信号, 默认动作是结束进程;
^C 终止进程 ^\ 终止进程并产生core
^z 暂停运行,无法直接kill掉, 而要kill -9 pid才行
fg [jobs_num] 调到前台运行 bg [jobs_num] 把挂起进程放到后台运行
直接在程序名后面加&运行 相当于 运行+^z挂起+bg后台运行的效果
运行起来后就可以直接kill掉了.
side effect 副作用, 有些函数说明里要有此项.
一般终端输入带有\n结尾, 接受字符串时要考虑这一点.
gdb ->set follow-fork-mode [child | parent ]调试模式设定;
register int i; 用寄存器而不用内存, 速度快.
信号量, PV操作. 原语操作, 不允许被打断的操作.
sem_init(&empty, 0, 1); //empty=1
sem_init(&full, 0, 0); //full=0
sem_wait(&empty); //P- 若empty<1在此等候
sem_post(&full); //V+
阅读(1173) | 评论(0) | 转发(0) |