问题解答:
1. exit(状态码)返回状态码有什么意义?
返回值被系统得到.系统根据状态码进行日志记录.
返回值被调用者得到:system/wait.程序会根据返回状态码进行对应处理。
exit(状态码)=main函数中的return 状态码;
2. 状态码的第二个字节才是exit()的返回值或者return值。
一、 进程的基本控制
1. 进程的常见控制函数
1.1 为什么需要控制进程?
1.2 pause/sleep/usleep(精确到微秒的sleep)
1.3 atexit on_exit 回调函数
程序在结束前会回调fun函数
#include <stdio.h>
#include <stdlib.h>
void fun()
{
printf("over\n");
}
int main()
{
atexit(fun);
printf("Process!\n");
return 0;
}
2. 进程与文件锁
在多进程下文件读写是共享的
问题:
怎么知道一个文件正在被另外进程读写?
解决方案:
文件锁。(建议锁)
API:
fcntl(文件锁受内核参数影响)
编程技巧:
对文件加锁
判定一个文件是否存在锁
函数说明:
int fcntl(
int fd, //被加锁的文件描述符号
int cmd, //锁的操作方式:F_SETLK(已经加锁,异常返回)
F_UNLK F_SETLKW(已经加锁,则阻塞等待)
struct flock *lk); //锁的描述
返回值:
0:加锁成功
-1::加锁失败
案例:
写两个程序:
A:加锁
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
main()
{
int fd;
struct flock lk;
int r;
//打开一个文件
fd=open("a.txt",O_RDWR);
if(fd==-1) printf(":%m\n"),exit(-1);
//描述锁
lk.l_type=F_WRLCK;//锁的类型,写锁
lk.l_whence=SEEK_SET;//锁的参照位置,绝对位置
lk.l_start=5;//锁的开始,第5个字节开始
lk.l_len=10;//锁的长度,10个字节
//锁的进程ID,系统定义
//加锁
r=fcntl(fd,F_SETLK,&lk);//加锁
if(r==0) printf("加锁成功!\n");
else printf("加锁失败!\n");
while(1);
//程序退出后系统会自动解锁
}
B:获取锁的信息
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
main()
{
int fd;
struct flock lk={0};//不初始化得到的信息会有误
int r;
//打开一个文件
fd=open("a.txt",O_RDWR);
if(fd==-1) printf(":%m\n"),exit(-1);
r=fcntl(fd,F_GETLK,&lk);//获取锁的信息
if(r==0) printf("得到锁成功!\n");
if(lk.l_type==F_WRLCK)
{
printf("写锁 \n");
}
printf("PID : %d \n", lk.l_pid);//加锁的进程id
}
锁也是一个进程可以共享的信息。
二、 信号
1. 信号的作用
背景:
进程之间通信比较麻烦,但进程之间有必须通信,比如父子进程之间。
作用:
通知其他进程响应。进程之间通信机制。
信号:
接受信号的进程马上停止,调用信号处理函数。
信号处理函数:
默认处理函数:打印信号信息,退出进程。
用户处理函数。
中断:(硬件的处理)把当前所有任务停止下来去处理当前信号任务
信号的终端处理是假的,称软中断。
案例:
1. 进程之中,默认信号处理
2. 进程之中,用户信号处理
3. 中断
kill -s 信号 进程ID
kill -信号 进程ID
信号表达方式:
数字形式 1-31和34-64
宏的方式 SIGINT=2
其中ctrl+c 发送的信号就是2( SIGINT)
kill -l察看所有信号
信号SIGKILL SIGSTOP不能被处理.
案例:
发送信号
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void handle(int s)
{
printf("我是信号发生!\n");
}
main()
{
signal(SIGINT,handle);
while(1)
{
printf("进程在执行:%d!\n",getpid());
sleep(1);
}
}
控制台每次输入 Ctrl+c 就会输出 我是信号发生! ,同样可以使用kill -SIGINT 进程ID
案例:使用程序发送信号
int kill(pid_t pid,int s)
进程ID(pid):
>0:发送信号到指定进程
=0:发送信号到该进程所在进程组的所有进程
-1:发送给所有进程,除init外
<0:发送给指定的进程组(组ID=绝对值)
2. 信号发送与安装
signal
kill
#include <stdio.h>
#include <signal.h>
main()
{
while(1)
{
kill(4601,SIGINT);
sleep(2);
}
}
3. 信号的应用
3.1.延时器timeout
SIGALRM(名字)
信号发出函数:alarm
unsigned int alarm(unsigned int seconds);
只向本进程发送信号,不向其他进程发送信号。
一个函数能绑定多个信号,一个信号只能绑定一个函数
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
void deal(int s)
{
printf("起床!\n");
}
main()
{
signal(SIGALRM,deal);
alarm(5);
while(1)
{
//.....
}
}
3.2.定时器
int setitimer(int which, //计时方式:
//ITIMER_REAL 自然界真实时间作为计时方式,推荐
//ITIMER_VIRTUA 由程序实际运行时间作为计时方式
//ITIMER_PROF 系统执行和进程执行混合计时方式
const struct itimerval *val, //定时器的时间参数
struct itimer *oldval); //返回原来设置的定时器
//如果=NULL,则不返回
struct itimerval{
struct timeval it_interval; //间隔时间
struct timeval it_value; //延时时间
}
struct timeval{
long tv_sec; //秒
long tv_usec; //微妙
}
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
void deal(int s)
{
printf("起床!\n");
}
main()
{
struct itimerval v={0};
signal(SIGALRM,deal);
//v.it_value.tv_sec=3;
v.it_value.tv_sec=0;
v.it_value.tv_usec=1;
v.it_interval.tv_sec=1;
//alarm(5);
setitimer(ITIMER_REAL,&v,0);
while(1)
{
//.....
}
}
3.3 信号应用:
系统与应用程序之间
应用于应用程序之间
父子进程之间
案例1:
使用定时器信号,实现多任务。
实现:
实现7位随机数
使用中断实现时间的显示
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <curses.h>
#include <math.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
WINDOW *wtime,*wnumb;
int num,i;
void showtime(int s)
{
time_t tt;
struct tm *t;
if(s==SIGALRM)
{
tt=time(0);
t=localtime(&tt);
mvwprintw(wtime,1,1,"%02d:%02d:%02d",t->tm_hour,t->tm_min,t->tm_sec);
refresh();
wrefresh(wtime);
wrefresh(wnumb);//¿ÉÑ¡
}
}
main()
{
struct itimerval val;
initscr();
curs_set(0);
signal(SIGALRM,showtime);
bzero(&val,sizeof(struct itimerval));
val.it_value.tv_sec=0;
val.it_value.tv_usec=1;
val.it_interval.tv_sec=1;
wnumb=derwin(stdscr,3,11,(LINES-3)/2,(COLS-11)/2);
wtime=derwin(stdscr,3,10,0,COLS-10);
box(wnumb,0,0);
box(wtime,0,0);
setitimer(ITIMER_REAL,&val,0);
refresh();
wrefresh(wtime);
wrefresh(wnumb);
while(1)
{
num=0;
for(i=0;i<7;i++)
{
num=num*10+rand()%10;
}
mvwprintw(wnumb,1,2,"%07d",num);
refresh();
wrefresh(wnumb);
wrefresh(wtime);
usleep(10000);
}
endwin();
}
案例2:
实现父子进程之间通信
控制进程。
sleep与pause函数被信号影响后,sleep不再继续sleep,pause不再pause.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <curses.h>
#include <math.h>
WINDOW *w;
int num,i;
int isstop=0;
void handle(int s)
{
if(s==SIGUSR1)
{
if(isstop==0)
{
isstop=1;
}
else
{
isstop=0;
}
}
}
main()
{
initscr();
curs_set(0);//隐藏光标
w=derwin(stdscr,3,11,(LINES-3)/2,(COLS-11)/2);//创建子窗体
keypad(stdscr,TRUE);
keypad(w,TRUE);
noecho();//禁止输入回显
box(w,0,0);//给子窗体加边框
refresh();
wrefresh(w);
if(fork())
{
signal(SIGUSR1,handle);
while(1)//显示7位数的随机数
{
if(isstop==1)
{
pause();//pause会被信号中断停止 ***
}
num=0;
for(i=0;i<7;i++)//产生7位随机数
{
num=num*10+rand()%10;
}
mvwprintw(w,1,2,"%07d",num);//显示随机数
refresh();//刷新屏幕。
wrefresh(w);
usleep(10000);//暂停10毫秒
}
}
else//child
{
//输入
int ch;
while(1)//处理按键
{
ch=getch();
//if(ch==KEY_ENTER)
{
kill(getppid(),SIGUSR1);
}
}
}
endwin();
}
练习:
在curses显示7位随机数
其他信号函数
raise
4. 信号的可靠与不可靠以及信号的含义
信号有丢失。(信号压缩)
由于历史的缘故:信号有压缩的需求.
可靠信号(实时信号)与不可靠信号(非实时信号).
早期信号 1-31 31个信号,不可靠(与系统有关).
后期信号34-64 31个信号,可靠信号(用户信号)
5. 信号的操作
信号导致的问题
5.1 信号屏蔽
int sigprocmask(int how, //操作方式
SIG_BLOCK //
SIG_UNBLOCK //
SIG_SETMASK //
const sigset_t *sigs, //操作的信号集合
sigset_t *oldsigs); //返回原来操作的信号集合
5.1.1.声明信号集合
sigset_t sigs;
5.1.2 加入屏蔽信号
一组信号集合维护函数:
1. 清空集合 sigemptyset
2. 添加信号到集合 sigaddset
3. 从集合删除信号 sigdelset
4. 添加所有信号到集合 sigfillset
5. 判定信号是否在集合 sigismember
5.1.3 屏蔽信号
5.1.4 解除屏蔽
#include <stdio.h>
#include <signal.h>
void h(int s)
{
printf("抽空处理int信号\n");
}
main()
{
int sum=0;
int i;
//1.
signal(SIGINT,h);
sigset_t sigs,sigp,sigq;
//2.
sigemptyset(&sigs);
sigemptyset(&sigp);
sigemptyset(&sigq);
sigaddset(&sigs,SIGINT);
//3.
sigprocmask(SIG_BLOCK,&sigs,0);
for(i=1;i<=10;i++)
{
sum+=i;
sigpending(&sigp);//返回在阻塞期间接收到阻塞信号的集合
if(sigismember(&sigp,SIGINT))
{
printf("SIGINT在排队!\n");
sigsuspend(&sigq);
//彩色字体部分控制中断位置在此
//使原来屏蔽信号无效,开放原来信号
//使新的信号屏蔽,
//当某个信号处理函数处理完毕
//sigsuspend恢复原来屏蔽信号,返回
}
sleep(1);
}
printf("sum=%d\n",sum);
sigprocmask(SIG_UNBLOCK,&sigs,0);
printf("Over!\n");
}
5.2 信号屏蔽的切换
int sigsuspend(sigset_t *sigs);
屏蔽新的信号,原来的信号失效.
sigsuspend
是阻塞函数,对参数信号屏蔽。
对参数没有指定的信号不屏蔽,但当没有屏蔽信号处理函数调用完毕
sigsuspend返回条件:
信号发生,并且信号是非屏蔽信号
信号必须要处理,而且处理函数返回后 sigsuspend才返回。
sigsuspend放在sigprocmask函数后使用时,sigsuspend设置新的屏蔽信号,保存旧的屏蔽信号。而且当sigsuspend返回的时候,会自动恢复sigprocmask旧的屏蔽信号。
#include <stdio.h>
#include <signal.h>
void h(int s)
{
printf("非屏蔽信号发生!\n");
}
main()
{
sigset_t sigs;
signal(SIGWINCH,h);
sigemptyset(&sigs);
sigaddset(&sigs,2);
printf("屏蔽开始!\n");
sigsuspend(&sigs);
printf("屏蔽结束!\n");
}
5.3 查询被屏蔽的信号
int sigpending(sigset_t *sets);
回顾:
- 进程控制sleep pause
- 理解信号的中断流程
- 发射信号(Shell/code),处理信号
- alarm与setitimer
- 信号应用:实现简单多任务与进程之间通信
- 使用信号+sleep/pause控制进程
- 信号的屏蔽
- 了解sigpending与 sigsuspend的使用.
思考:
信号处理函数调用过程中,是否被其他信号影响.