Unix编程——进程


文章地址

程序中用到的两个头文件:

ourhdr.h

#ifndef __ourhdr_h
#define __ourhdr_h
#include         <sys/types.h>
#include         <stdio.h>
#include         <stdlib.h>
#include         <string.h>
#include         <unistd.h>
#define MAXLINE 4096  
#define FILE_MODE        (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
  
#define DIR_MODE         (FILE_MODE | S_IXUSR | S_IXGRP | S_IXOTH)
  
typedef void Sigfunc (int);

  
#if      defined(SIG_IGN) && !defined(SIG_ERR)
#define SIG_ERR ((Sigfunc *)-1)
#endif
#define min(a,b)         ((a) < (b) ? (a) : (b))
#define max(a,b)         ((a) > (b) ? (a) : (b))
  
char *path_alloc (int *);
int open_max (void);  
void clr_fl (int, int);  
void set_fl (int, int);  
void pr_exit (int);  
void pr_mask (const char *);
Sigfunc *signal_intr (int, Sigfunc *);
int tty_cbreak (int);  
int tty_raw (int);  
int tty_reset (int);  
void tty_atexit (void);  

#ifdef   ECHO   
struct termios *tty_termios (void);

#endif
void sleep_us (unsigned int);
ssize_t readn (int, void *, size_t);
ssize_t writen (int, const void *, size_t);
int daemon_init (void);  
int s_pipe (int *);  
int recv_fd (int, ssize_t (*func) (int, const void *, size_t));

  
int send_fd (int, int);  
int send_err (int, int, const char *);
int serv_listen (const char *);
int serv_accept (int, uid_t *);
int cli_conn (const char *);
int buf_args (char *, int (*func) (int, char **));

  
int ptym_open (char *);  
int ptys_open (int, char *);

#ifdef   TIOCGWINSZ
pid_t pty_fork (int *, char *, const struct termios *, const struct winsize *);

#endif
int lock_reg (int, int, int, off_t, int, off_t);

  
#define read_lock(fd, offset, whence, len) \
   lock_reg (fd, F_SETLK, F_RDLCK, offset, whence, len)
#define readw_lock(fd, offset, whence, len) \
   lock_reg (fd, F_SETLKW, F_RDLCK, offset, whence, len)
#define write_lock(fd, offset, whence, len) \
   lock_reg (fd, F_SETLK, F_WRLCK, offset, whence, len)
#define writew_lock(fd, offset, whence, len) \
   lock_reg (fd, F_SETLKW, F_WRLCK, offset, whence, len)
#define un_lock(fd, offset, whence, len) \
   lock_reg (fd, F_SETLK, F_UNLCK, offset, whence, len)
pid_t lock_test (int, int, off_t, int, off_t);

  
#define is_readlock(fd, offset, whence, len) \
   lock_test (fd, F_RDLCK, offset, whence, len)
#define is_writelock(fd, offset, whence, len) \
      lock_test (fd, F_WRLCK, offset, whence, len)
void err_dump (const char *, ...);
void err_msg (const char *, ...);
void err_quit (const char *, ...);
void err_ret (const char *, ...);
void err_sys (const char *, ...);
void log_msg (const char *, ...);
void log_open (const char *, int, int);
void log_quit (const char *, ...);
void log_ret (const char *, ...);
void log_sys (const char *, ...);
void TELL_WAIT (void);  
void TELL_PARENT (pid_t);
void TELL_CHILD (pid_t);
void WAIT_PARENT (void);
void WAIT_CHILD (void);
#endif

my_err.h

#include <errno.h> /* for definition of errno */
#include <stdarg.h> /* ISO C variable aruments */

static void err_doit(int, int, const char *, va_list);

/*
 * Nonfatal error related to a system call.
 * Print a message and return.
 */
void
err_ret(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
}


/*
 * Fatal error related to a system call.
 * Print a message and terminate.
 */
void
err_sys(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    exit(1);
}


/*
 * Fatal error unrelated to a system call.
 * Error code passed as explict parameter.
 * Print a message and terminate.
 */
void
err_exit(int error, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, error, fmt, ap);
    va_end(ap);
    exit(1);
}


/*
 * Fatal error related to a system call.
 * Print a message, dump core, and terminate.
 */
void
err_dump(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    abort(); /* dump core and terminate */
    exit(1); /* shouldn't get here */
}


/*
 * Nonfatal error unrelated to a system call.
 * Print a message and return.
 */
void
err_msg(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
}


/*
 * Fatal error unrelated to a system call.
 * Print a message and terminate.
 */
void
err_quit(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
    exit(1);
}


/*
 * Print a message and return to caller.
 * Caller specifies "errnoflag".
 */
static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char buf[MAXLINE];
   vsnprintf(buf, MAXLINE, fmt, ap);
   if (errnoflag)
       snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s",
         strerror(error));
   strcat(buf, "\n");
   fflush(stdout); /* in case stdout and stderr are the same */
   fputs(buf, stderr);
   fflush(NULL); /* flushes all stdio output streams */
}

exit函数

终止方式分为两种:

  • 正常终止:
    • main函数中执行return
    • 调用exit函数,该函数会关闭所有标准I/O
    • 调用_exit系统调用函数,此函数由exit函数调用
  • 异常终止
    • 调用abort,它产生一个SIGABRT信号
    • 当进程接收到某个信号时

不管进程如何终止,最后都会执行同一行代码,这段代码会为相应进程关闭所有打开的描述符,释放它所使用的存储器

进程为了通知它的父进程自己是如何终止的,在终止的时候会返回一个退出状态终止状态,父进程可以使用waitwaitpid函数来获得其子进程的终止状态

退出状态终止状态是有区别的:
在最后调用_exit时内核将其退出状态转换成终止状态
一个C程序是如何启动和终止的

内核使程序执行的唯一方法是调用一个exec函数
进程自愿终止的唯一方式时调用一个_exit(exit)函数

atexit函数

从入中可以看到,进程在调用exit函数时,exit函数又调用了一系列
终止处理程,这些终止处理程序是由atexit函数负责注册的,ANSI C规定一个进程至多注册32个函数,这些函数由exit自动调用

atexit函数:
#include <stdlib.h>
int atexit(void (*func)(void)) ;

该函数的参数是一个函数地址,作为参数的那个函数地址所指向的函数无参且无返回值atexit成功返回0,否则为1

atexit函数使用示例:

#include "ourhdr.h"
#include "my_err.h"

static void my_exit1(void);
static void my_exit2(void);

int main(void)	
{
	if(atexit(my_exit2) != 0)
		err_sys("can't register my_exit2");
	
	if(atexit(my_exit1) != 0)
		err_sys("can't register my_exit1");
	
	printf("main is done\n");
	return 0;
}

static void my_exit1(void)
{
	printf("first exit handler\n");
}

static void my_exit2(void)
{
	printf("second exit handler\n");
}

现在回来接着说exit函数,上面说父进程接收子进程的退出状态,但是如果父进程先于子进程结束呢?

  • 在这种情况下,子进程的父进程会自动改变为init进程,它的PID为1

UNIX中,一个已经终止,但是其父进程却没有给它收尸的进程,被称作僵尸进程(Zombie),这里说的没有收尸,意思就是父进程没有调用waitwaitpid函数来对子进程进行回收操作,回收操作具体为:

获取终止子进程的有关信息、释放它仍占用的资源

wait、waitpid函数

现在我们来说一下waitwaitpid函数

当进程终止时(不管是正常终止还是异常终止),内核都会向其父进程发送SIGCHLD信号,
因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生 ),所以这种信号也是内核向父进程发的异步通知

对于该信号,父进程有两种处理选项

  • 不处理,忽略信号
  • 为该信号提供一个处理函数

调用wait或者waitpid的进程会出现以下这三种状况:

  • 阻塞(如果其所有子进程都还在运行)
  • 带着子进程的终止状态立即返回(如果一个子进程已终止,正等待父进程存取其终止状态)
  • 出错立即返回(如果它没有任何子进程)

如果我们随意调用wait函数,很可能会导致进程阻塞,因为此时并不一定有子进程结束,也就是说没有SIGCHLD信号发出

UNIX中,使用以下三个宏来获取终止状态,他们定义在<sys/wait.h>头文件中,均以WIF开头

说 明
WIFEXITED(status)若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(status)取子进程传送给exit_exit参数的低8
WIFSIGNALED(status)若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSIG(status)取使子进程终止的信号编号。另外,SVR4和4.3+ BSD(但是,非POSIX.1)定义宏:WCOREDUMP(status),若已产生终止进程的core文件,则它返回真
WIFSTOPPED(status)若为当前暂停子进程的返回的状态,则为真。对于这种情况,可执行WSTOPSIG(status)取使子进程暂停的信号编号

下面是关于wait函数和这三个宏的示例程序:

#include <sys/wait.h>
#include "ourhdr.h"
#include "my_err.h"
void pr_exit(int status) 
{
	if(WIFEXITED(status))
		printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
	else if(WIFSIGNALED(status))
		printf("abnormal termination, signal number = %d%s\n", WTERMSIG(status),
#ifdef WCOREDUMP
		WCOREDUMP(status) ? " (core file.generated)" : "");
#else 
		"");
#endif
	else if(WIFSTOPPED(status))
		printf("child stopped, signal number = %d\n", WSTOPSIG(status));
}

int main(void)
{
	pid_t pid;
	int status;
	
	if((pid =fork()) < 0)
		err_sys("fork error");
	else if(pid == 0)
		exit(7);
	
	if(wait(&status) != pid)
		err_sys("wait error");
	pr_exit(status);
		
		if((pid = fork()) < 0)
		err_sys("fork error");
	else if(pid == 0)
		abort();
	
	if(wait(&status) != pid)
		err_sys("wait error");
	pr_exit(status);
	
	if((pid = fork()) < 0)
		err_sys("fork error");
	else if(pid == 0)
		status /= 0;
	
	if(wait(&status) != pid)
		err_sys("wait error");
	pr_exit(status);
	
	exit(0);
}

执行结果:

[root@localhost unix环境高级编程]# ./a.out
normal termination, exit status = 7
abnormal termination, signal number = 6 (core file.generated)
abnormal termination, signal number = 8 (core file.generated)

wait函数和waitpid函数的区别

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
										两个函数返回:若成功则为进程ID,若出错则为-1

它俩有一个共同的参数statloc,一个int指针,它所指向的内存单元是用来存放子进程的终止状态的,如果不关心子进程的终止状态,则设为null即可

  • 在一个子进程终止前,使其调用者阻塞,而waitpid有一选择项,可使调用者不阻塞
  • waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的进程

上面的意思就是说,对于wait函数,只要有一个子进程结束,它就会返回,如果我们想要等待一个特定的子进程,就需要重复调用wait函数,把每次的返回结果和我们的期望值作比较,直到得到我们想要的那个进程

这样一来就比较麻烦了,其实我们只需要一个可以等待特定PID的函数即可,waitpid就应运而生了

对于waitpidpid参数:

  • pid == -1 等待任一子进程。于是在这一功能方面waitpidwait等效
  • pid > 0 等待其进程IDpid相等的子进程
  • pid == 0 等待其组ID等于调用进程的组ID的任一子进程
  • pid < -1 等待其组ID等于pid绝对值的任一子进程

swaitpid函数的返回值也是终止进程的pidwaitpid函数和wait函数的令一点区别是对于wait函数,只有调用进程没有子进程时才会出错,而对于waitpid'函数,除了这种情况,如果指定的进程ID或进程组不存在,也会导致函数报错

waitpidoptions参数:

常数说 明
WNOHANG若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时其返回值为0
WUNTRACED若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程

一个程序示例,老实说,我并没有看懂这程序到底想干什么,只是直到爷爷进程终止了,孙子进程才结束,而且孙子进程还是一个僵尸进程,由于init(systemd)进程回收

#include <sys/wait.h>
#include "ourhdr.h"
#include "my_err.h"
 
int main(void)
{
	pid_t pid;
	
	if( (pid = fork()) < 0)
		err_sys("fork error");
	else if(pid == 0) {
		/***子进程(son)又创建了一个子进程(grandson)
		然后son调用exit函数退出,grandson成为僵尸进程
		下面的printf输出的父进程应该是init进程
		(在rhat的高版本中,init进程已经不存在了,取而代之的是systemd进程)
		它会自动领养僵尸进程
		***/
		if((pid = fork()) < 0)
			err_sys("fork error");
		else if(pid > 0)
			exit(0);
		
		sleep(2);
		printf("second child, parent pid = %d\n", getppid());
		exit(0);
	}
	
	if(waitpid(pid, NULL, 0) != pid) 
		err_sys("waitpid  error");
	
	exit(0);      
}

竞态条件

多个进程都企图对共享数据进行某种处理,而最后的结果又取决于
进程运行的顺序时,则我们认为这发生了竞态条件race condition

竞态条件测试代码:

#include "ourhdr.h"
#include "my_err.h"

static void charatatime(char *);

int main(void) 
{
	pid_t  pid;
	if((pid = fork()) < 0)
		err_sys("foek error");
	else if(pid == 0) 
		charatatime("output from child\n");
	else 
		charatatime("output from parent\n");
	exit(0);
}

static void charatatime(char *str) 
{
	char 	*ptr;
	int 	c;
	
	//将标准输出的缓存大小设为空
	//这样每次输出字符都会调用一次write,这样写操作的速度就会变得非常慢
	//进程切换的频率也会相应上升
	setbuf(stdout, NULL);
	for(ptr=str; c=*ptr++;) 
		putc(c, stderr);
}

运行结果,几乎每次都会切换进程:

root@x:/mnt/d/TempFiles/unix环境高级编程# ./a.out
oouuttppuutt  ffrroomm  pcahrielndt

避免出现竞态条件的方法:

#include "ourhdr.h"
#include "my_err.h"

static void charatatime(char *);

int main(void) 
{
	pid_t  pid;
	
	TELL_WAIT();	//set things up for TELL_XXX and WAIT_XXX
	
	if((pid = fork()) < 0)
		err_sys("foek error");
	else if(pid == 0) { 
		WAIT_PARENT();		//wait for parent getting done what it need to do
		charatatime("output from child\n");
	}
	else {
		charatatime("output from parent\n"); 
		TELL_CHILD(pid); //tell child we're done
	}
	exit(0);
}

static void charatatime(char *str) 
{
	char 	*ptr;
	int 	c;
	
	setbuf(stdout, NULL);
	for(ptr=str; c=*ptr++;) 
		putc(c, stderr);
}

上面的那些TELL_XXXWAIT_XXX需要自己实现,注释说明了对这些函数的需求

上面修改后的程序会使得父进程先运行,若修改成下面这样:

else if(pid == 0) {
	charatatime("output from child\n");
	TELL_PARENT(getppid());		//tell parent we're done
}
else {
	WAIT_CHILD();	//wait for child getting done what it need to do
	charatatime("output from parent\n"); 
}

进程间通信

管道

  • 管道是半双工的,数据的传输是单向的,一端输入,另一端输出。需要双方通信时,需要建立起两个管道
  • 管道分为普通管道和命名管道
  • 普通管道位于内存,只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程
  • 命名管道位于文件系统,没有亲缘关系的进程间只要知道管道名也可以通讯
  • 管道也是文件。管道大小为**4096字节**
  • **单独构成一种独立的文件系统。**管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中
  • 数据的读出和写入,一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据
  • 管道满时,写阻塞管道空时,读阻塞

普通管道的创建方法:

#include <unistd.h>
int pipe(int fd[2])    //成功返回0,失败返回-1

pipe函数用来创建一个管道,fd数组是传出参数,用于保存返回的两个文件描述符,该文件描述符用于标识管道的两端,fd[0]只能用于读,fd[1]只能用于写,记作小读大写

我们尝试往fd的小端写入数据:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <errno.h>

int main() {
	int fd[2];

	if (pipe(fd) < 0) {
		std::cout<<"create pipe failed."<<std::endl;
		return -1;
	}

	char *temp = "hello world";

	if (write(fd[0], temp, strlen(temp) + 1) < 0) {
		std::cout<<"write pipe failed:"<<strerror(errno)<<std::endl;
	}

	return 0;
}

输出:使用g++编译,因为上面使用了iostream头文件

write pipe failed:Bad file descriptor

内核对于管道的fd[0]描述符打开的方式是以只读方式打开的,同理fd[1]是以只写方式打开的,所以管道只能保证单向的数据通信

图示:

管道的一般使用场景:

由于子进程拥有父进程各种资源的拷贝,所以父子进程共享同一个由父进程创建的管道,并通过该管道进行通信

为了形成单向通信,父子进程需要各自关闭一个端口,以保证数据传输的正确性,通常情况下,数据流的方向为父进程到子进程

父子进程通过管道通信,测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
    int pfd[2]; //保存打开管道后的两个文件描述符
    pid_t cpid; //保存子进程标识符
    char buf;
    if(argc != 2)//判断命令行参数是否符合
    {
        fprintf(stderr,"Usage: %s <string>\n",argv[0]);
        exit(0);
    }
    if (pipe(pfd) == -1)//建立管道
    {
        perror("pipe");
        exit(EXIT_FAILURE);
    } 
    if ((cpid = fork()) == -1)
    {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    if (cpid == 0) //子进程
    {
        close(pfd[1]);          //关闭管道写,引用计数-1 
        while (read(pfd[0], &buf, 1) > 0)   //从管道循环读取数据
            write(STDOUT_FILENO, &buf, 1);  //输出读到的数据
        write(STDOUT_FILENO, "\n", 1);      //输出从管道读取的数据
        close(pfd[0]);         //关闭管道读,引用计数-1 
        exit(0);
    }
    else  //父进程
    {
        close(pfd[0]);        //关闭管道读 
        write(pfd[1], argv[1], strlen(argv[1]));//向管道写入命令行参数1
        close(pfd[1]);          
        wait(NULL);           //等待子进程退出
        exit(0);
    }
}

可以看到,父进程写入管道的内容,被子进程读取并输出

使用管道实现父子进程间的双向通信

如图,我们需要创建两个管道以实现父子进程间的双向通信

创建两个管道fd1fd2父进程中关闭fd1[0],fd2[1],子进程中关闭fd1[1],fd2[0],这样父子进程就和两条管道组成了一个闭环

父子进程双向通信测试代码:

#include <iostream>  
#include <unistd.h>  
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
  
int main()  
{  
    int fd1[2], fd2[2];  
  
    if (pipe(fd1) < 0 || pipe(fd2) < 0)  
    {  
        std::cout<<"create pipe failed."<<std::endl;  
        return -1;  
    }  
  
    char buf[256];  
    char *temp = "hello son";  
  
    if (fork() == 0)  
    {  
		//子进程关闭fd1的写端和fd2的读端
        close(fd1[1]);  
        close(fd2[0]);  
  
		//从管道fd1读取数据,放入buf中
        read(fd1[0], buf, sizeof(buf));  
        std::cout<<"child:receive message from pipe 1: "<<buf<<std::endl;  
  
		//从temp中向管道fd2写数据
        write(fd2[1], temp, strlen(temp) + 1);  
        exit(0);  
    }  
  
	//以下是父进程才会执行的代码
	//过程和上面很类似
	//关闭管道1的读端和管道2的写端,从向管道1写数据,从管道2读数据
    close(fd1[0]);  
    close(fd2[1]);  
  
    write(fd1[1], temp, strlen(temp) + 1);  
    read(fd2[0], buf, sizeof(buf));  
    std::cout<<"parent:receive message from pipe 2: "<<buf<<std::endl;  
  
    return 0;  
}

结果:

child:receive message from pipe 1: hello son

parent:receive message from pipe 2: hello son

在本程序中,子进程会一直阻塞直到父进程向管道中写入数据

popen和pclose函数

标准I/O函数库提供的popen函数就是管道的一个实例,该函数创建一个管道,并fork一个子进程,该子进程根据popen传入的参数,关闭管道的对应端,然后执行传入的shell命令,然后等待终止

关于这两个函数的介绍:

#include <stdio.h>  
FILE *popen(const char *command, const char *type);    
/***
成功返回标准文件I/O指针,失败返回NULL
command:
	该传入参数是一个shell命令行,这个命令是通过shell处理的
type:
	该参数决定调用进程对要执行的command的处理,type有如下两种情况:
	type = "r"
		调用进程将读取command执行后的标准输出,该标准输出通过返回的FILE*来操作
	type = "w"
		调用进程将写command执行过程中的标准输入
***/
		
int pclose(FILE *stream);
/***
成功返回shell的终止状态,失败返回-1
pclose函数会关闭由popen创建的标准I/O流,等待其中的命令终止,然后返回shell的执行状态
***/

popen的测试代码

#include <iostream>  
#include <cstdio>  
#include <unistd.h>  
  
using namespace std;  
  
int main()  
{  
    char *cmd = "ls /usr/include/sys*.h";  
  
	//由popens生成的子进程执行cmd命令,并通过FILE*指针返回该命令的输出
    FILE *p = popen(cmd, "r");  
    char buf[256];  
  
    while (fgets(buf, 256, p) != NULL)  
    {  
        cout<<buf;  
    }    
  
    pclose(p);  
  
    return 0;  
}

程序运行结果:

/usr/include/syscall.h
/usr/include/sysexits.h
/usr/include/syslog.h

下面看一下typew时的效果:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main( void )
{
    FILE   *stream;//文件流
    char   buf[1024];//读写缓冲区
    memset( buf, '\0', sizeof(buf) );//清空
    stream = popen( "ls /usr/include/sys*.h", "w" );
    for(;;)
    {
        scanf("%s", buf);//接受输入
        if(strcmp(buf,"k") == 0)//如果是k就退出
        {
            break;
        }
        fprintf(stream,"%s\n",buf);//写入
        memset(buf, 0, sizeof(buf));
    }
    pclose( stream );//关闭
    return 0;
}

我们可以向子进程执行的shell命令进行输入

信号

SIGUSR

signal函数

函数原型:

#include <signal.h>
void (*signal(int signo, void(*func)(int))) (int);
						返回:成功则为以前的信号处理配置,若出错则为 `SIG_ERR`

signo参数是信号名,参见kill -l

func的值是:

  1. 常数SIG_IGN,向内核表示忽略此信号
  2. 常数SIG_DFL,表示接到此信号后的动作是系统默认动作
  3. 当接到此信号后要调用的函数的地址。

如果指定 SIG_IGN,则需要注意有两个信号SIGKILLSIGSTO不能忽略

当指定函数地址时,我们称此为捕捉此信号。我
们称此函数为信号处理程序(signal handler)信号捕捉函数(signal-catching function)

这样表示原型比较复杂,使用typedef来简化:

typedef void Sigfunc(int)

这样定义完之后,Sigfunc sig就相当于void sig(int),其实就是把typedef语句中的Sigfunc替换成sig

我来稍微解释一下signal函数,它有两个参数,一个是整型数,一个函数指针,然后返回值是一个函数指针,该指针所指向的函数参数是一个整型数,且无返回值

一个测试函数:

#include <signal.h>
#include "ourhdr.h"
#include "my_err.h"

static void sig_usr(int);

int main(void) 
{
	if(signal(SIGUSR1, sig_usr) == SIG_ERR)
		err_sys("can't catch SIGUSR1");
	
	if(signal(SIGUSR2, sig_usr) == SIG_ERR)
		err_sys("can't catch SIGUSR2");
	
	for(;;)
		pause();
}

static void sig_usr(int signo)
{
	if(signo == SIGUSR1)
		printf("received SIGUSR1\n");
	else if(signo == SIGUSR2)
		printf("received SIGUSR2\n");
	else
		err_dump("received signal %d\n", signo);
	return;
}

运行结果:让程序在后台运行,这样能看到它的PID

root@x:/mnt/d/TempFiles/unix环境高级编程# ./a.out &
[2] 79
root@x:/mnt/d/TempFiles/unix环境高级编程# kill -USR1 79
root@x:/mnt/d/TempFiles/unix环境高级编程# received SIGUSR1
kill -USR2 79
root@x:/mnt/d/TempFiles/unix环境高级编程# received SIGUSR2

SIGALRM

使用alarm函数可以设置一个时间值 (闹钟时间),在将来的某个时刻该时间值会被超过。当所设置的时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程

#include <unistd.h>
unsigned int alarm(unsigned int seconds) ;
							返回: 0或以前设置的闹钟时间的余留秒数

每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前登记的闹钟时间则被新值代换

意思就是如果上次的定时时间还没到,则alarm函数的返回值就是上次定时的剩余时间,否则就会返回0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值