1、Linux基本语法
1.1 make命令
make 命令执行 make target 格式 eg make clean 执行rm -f *.omake test 执行gcc test.o pro1.o pro2.o -o test
1.2 lib库函数
关于 gcc中 调用math.h中的函数 gcc -c -o 遇到的问题 eg gcc math_demo1.c -o math_demo1 -lm 要加lm 其中 lm为libmath的简称 在gcc -o 生成lib的时候的命名时 为libxx简称为lx eg libmath简称lm
1.3 一些函数
1.3.1 chmod
S_IRUSR 所有者具有读取权限 S_IWUSR 所有者具有写权限 S_XUSR 所有者具有执行权限
USR usr所有者 GRP group组 OTH 用户 R读 W写 X执行
1.3.2 open create
O_RDONLY 以只读模式打开 O_WRONLY 以写入模式打开 O_RDWR 以读写模式打开 O_APPEND 在文件尾写入数据
O_TRUNG 设置文件的长度为0 并且舍弃现存的数据 O_CREAT 建立文件 使用mode参数设置访问权限 O_EXCL 与O_CREAT一起使用
1.3.3 fcntl文件锁
1、fcntl文件上锁 避免共享的资源产生竞争,导致数据读写发生错误。 当一个文件被加上强制锁之后,内核将阻止其他任何文件对器进行读写操作
flock结构体 p192 Linux程序设计第二版 flock函数用于锁定文件或者解除锁定 关于flock和fcntl函数的说明p193
2、fopen函数 带缓冲的文件I/O操作 p194 fgets函数在p199 用户从文件中读取一字符串
2、 进程控制
2.1 进程简介
1、进程 有手工启动和调度启动两种 at命令 用于在未来某个时间执行某个任务
exec函数 在进程中启动另一个程序执行 exelc有6个函数来建立子进程 分别是execl execv execle execve execlp execvp
2、fork函数从已存在的进程中复制一个新进程 waitpid暂停父进程 等待子进程运行完成
为了执行一个程序 需要一种机制使子进程成为要执行的命令 Linux系统调用exec完成这项工作,进程可以用另外一个程序的可执行代码来覆盖自身。
2.2 僵尸进程
1、僵尸进程 p229 一个已经终止运行 但其父进程尚未对其进行善后处理 eg释放他占用的资源
fork函数创建子进程后,由于子进程有可能比父进程晚终止 ,即父进程终止后,子进程没有终止 可以在父进程调用wait 或 waitpid函数
wait函数的作用是使父进程阻塞,直到一个子进程终止或者该进程接到来一个指定的信号为止。waitpid和wait函数的作用相同,但其不一定要等待第一个子进程终止
2.3 守护进程
2.3.1 守护进程
守护进程 daemon p239 是运行在后台,并且一直在运行的特殊进程 独立于控制终端并且周期性执行某种任务或者等待处理某些发生的时间。守护进程是一个很有用的进程,Linux绝大多数的服务器就是用守护进程实现的,比如作业控制进程crond,web服务器httpd,internet服务器inetd等等.
守护进程必须要与其他运行钱的环境隔离开来,这些环境包括未关闭的文件描述符,控制终端,会话等。
2.3.2 守护进程的编写
1、创建子进程 终止父进程
创建子进程 终止父进程 ,由于守护进程是脱离控制终端的,因此首先创建子进程,终止父进程,造成一个已经运行完毕的假象。
创建子进程,终止父进程的代码如下:
pid=fork();
if(pid>0){
exit(0);//终止父进程
}
2、在子进程中创建新会话
这个步骤是创建守护进程中的最重要的一步,在这一步使用的是系统函数setsid,该函数用于创建一个新的会话,并且担任该会话组的组长。调用setsid函数有三个作用:让进程摆脱原会话的控制、让进程摆脱原进程组的控制和让进程摆脱原终端控制终端的作用。
在调用fork函数时,子进程全盘拷贝之父进程的会话期session、进程组、控制终端等。setsid函数使得进程完全独立出来,从而脱离所有其他进程的控制。
3、改变工作目录
使用fork创建的子进程也继承来父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统不能卸载,因此,把当前工作目录换成其他路径 eg:/ 或/tmp 。改变工作目录常见的函数是chdir.
4、重设文件创建掩码
由于使用fork函数创建的新进程继承了父进程的文件创建掩码,这就给该子进程使用文件带来了诸多麻烦,因此,把文件创建掩码设置为0,可以大大增加该守护进程的灵活性。设置文件创建掩码的函数是umask.通常使用方法是为umask(0);
5、关闭文件描述符
用fork函数新建的子进程会从父进程那里继承一些已经打开来的文件,在这些被打开的文件可能永远不会被守护进程读写,但他们一样会消耗系统资源,可能导致所在的文件系统无法卸载。因此一般要关闭文件描述符:
for(i=0;i<NOFILE;i++)
cloes(i);
//也可以按找如下方式
for(int i=0;i<MAXFILE;i++)
cloes(i);
2.3.3 守护进程的一个demo
//初始化一个精灵进程
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
void daemonize(){
pid_t pid;
//调用fork创建新进程 返回值存在pid中 返回两个值 返回值为0代表子进程 返回值大于0代表父进程
if((pid==fork())<0){
perror("fork failed");
exit(1);
}
else if(pid!=0){//父进程
exit(0); //创建子进程,终止父进程
}
//在子进程中创建一个新的对话
setsid();
//改变当前目录到 "/"
chdir("/");
//重设文件创建掩码
umask(0); /* clear file mode creation mask */
//关闭文件描述符
for(i=0;i<NOFILE;i++)
cloes(i);
//标准输入、标准输出、错误输出重定向到空设备 黑洞 /dev/null中
open("/dev/null",O_RDWR);
/*
函数dup和dup2提供了复制文件描述符的功能
输入重定向:关闭标准输入设备,打开(或复制)某普通文件,使其文件描述符为0.
输出重定向:关闭标准输出设备,打开(或复制)某普通文件,使其文件描述符为1.
错误输出重定向:关闭标准错误输入设备,打开(或复制)某普通文件,使其文件描述符为2.
*/
dup2(0,1);
dup2(0,2);
}
2.3.4 守护进程注意要点
按照守护进程的惯例,通常将当前工作目录切换到根目录,将文件描述符0,1,2 重定向到/dev/null
父进程创建来子进程,而父进程退出之后,此时该子进程变成来孤儿进程。在linux中,每当系统发现孤儿进程之后,signal就自动由1号进程(也就是init进程)收养他,原先的子进程就会变为init进程的子进程。
在linux系统中,程序变为守护进程之后,完全脱离来终端的控制,调试的时候也没法像普通程序那样调试,用gdb也无法正常调试。
因此,编写调试守护进程,一般是利用系统日志服务,通过系统守护进程syslogd控制自己编写的守护进程的告警信息。linux C语言中,只要调用syslog函数,将守护进程的出错信息写入”/var/log/message”系统日志文件中,就可以做到对守护进程的调试。
2.3.5 syslog函数介绍说明
其功能是将信息记录至系统日志文件 void syslog(int priority ,char *format,...);
其中priority指定信息的种类等级,主要有LOG_INFO 提示相关信息,LOG_DEBUG 出错信息,format参数和printf函数参数相同。
注意:调用openlog、syslog函数,操作的系统日志文件”/var/log/message”,必须要有root权限。
3、进程通信
3.1进程间通信
每个进程有各自的进程地址空间,任何一个进程的全局变量在另一个进程中都不能访问,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区。能实现进程间通信的方法有:信号signal、管道pipe、套接字socket和System V IPC机制(包括消息队列、信号量、共享内存)。
3.1.1 信号signal
信号可以在任何时候发给某个进程,而无需知道该进程的状态,如该进程当前处于未执行状态,则该信号内核保存起来,直到该进程恢复执行并传递给他为止。
Linux提供来十几种信号,信号之间依靠他们的值来区分,但是通常在程序中使用信号的名字来代表一个信号。通常程序中不需要直接包含/usr/include/bits/signum.h头文件,而是应该包含<signal.h>
3.1.2 Linux系统常见的信号的含义及其默认操作
信号名 | 含义 | 默认操作 |
---|---|---|
SIGQUIT | 该信号在用户按下退出键(crtl+\),造成系统非正常终止 | 终止 |
SIGKILL | 一个特殊信号,用来立即结束程序的运行,并且不能被阻塞,处理,或忽略 | 终止 |
SIGTERM | 程序结束信号, 与SIGKILL不同的是该信号可以被阻塞和 处理. 通常用来要求程序自己正常退出. | 终止 |
SIGSTOP | 特殊信号,用于暂停一个进程,并且不能被阻塞,处理,或忽略 | 暂停进程 |
SIGALRM | 该信号在一个定时器计时完成时发生,定时器可以用进程调用alarm函数来设置 | 终止 |
3.1.3 信号操作的相关函数
函数 | 功能 |
---|---|
kill | 发送信号SIGKILL给进程或进程组 |
signal | 捕捉信号SIGINT 、SIG_IGN、SIG_QUIT时执行信号处理函数 |
alarm | 定时器时间到时,向进程发送SIGALARM信号 |
具体详细内容参见《Linux程序设计》p256-257
1、信号发送
kill函数用于给特定的进程发送信号,raies函数用于向某个进程自身发送信号。
2、信号处理
//主程序无限循环,用户按下ctrl+c发送信号SIGINT ,此时调用fun_ctrl_c处理函数,设置信号处理的函数是signal
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void fun_ctrl_c(){//自定义信号处理函数
printf("按下来ctrl+c");
puts("有什么要处理的,在处理信号中编程");
(void)signal(SIGINT,SIG_DFL);
}
int main(){
(void)signal(SIGINT,fun_ctrl_c);//按下了ctrl+c 调用fun_ctrl_c函数
printf("主程序,无限循环");
while(1){
}
return 0;
}
3.2 管道
3.2.1 无名管道相关操作
pipe用于相关进程之间的通信,如父子进程,通过pipe()系统调用来创建并打开,最后一个进程关闭对他的引用时,pipe自动撤销。
管道的实质是一个内核缓冲区,进程可以以先进先出的方式从缓冲区中存取数据
无名管道的操作 见书p269页
char buf_r[100];
memset(buf_r,0sizeof(buf_r));//把buf_r所指的内存区域前sizeof(buf_r)得到的字节置为0,初始化清空
memset函数 将一段内存空间填入某值
3.2.2 高级管道相关操作
在linux c程序设计中,除了常用的pipe系统调用来建立管道外,还可以使用管道库函数popen函数来创建管道。
#include<stdio.h>
int main(){
FILE *fp;
int num;
char buf[5000];
memset(buf,0,sizeof(buf));//初始化清空操作
puts("建立管道");
fp=open("ls -l","r");//调用popen函数,建立管道(读管道)
if(fp!=null){
num=fread(buf,sizeof(char),5000,fp);
if(num>0){
puts("命令为'ls -l',运行结果如下: ");
puts(buf);
}
pclose(fp);
}
else{
puts("创建管道失败");
return 1;
}
fp=popen("grep lm.c","w");//调用popen函数,建立管道(写管道
puts("第二个命令 'grep lm.c' 运行结果如下")
fprintf(fp,"%s\n",buf);
pclose(fp);
return 0;
}
popen函数相关介绍在p283,其中 FILE fopen(const char command,const char type),在编写具有SUID/SGID 权限的程序时,尽量避免使用popen(),因为popen()会继承环境变量。通过环境变量会造成系统安全问题。
3.3 消息队列
消息队列就是一个消息的链表,是一系列保存在内核中的消息的列表。Linux采用消息队列的方式来实现消息传递,发送方可以不必等待接收方检查它所收到的消息就可以继续工作下去,而接收方如果如果没有收到消息也不需要等待。新的消息总是在队列的末尾,接受的时候并不总是从同来接收,可以从中间接收。
3.4 共享内存
共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身地址空间中。
内存映射机制使进程之间通过映射同一个普通文件实现共享内存,通过mmap()系统调用来实现。普通文件被映射到地址空间后,进程可以像访问普通内存一样对文件进行访问,不必在调用read和write等文件操作。
注:linux程序设计暂时到这,其他的如多线程,网络编程等技术,暂时不用,到时候用的时候在更新。
本篇blog参考书籍为《linux程序设计第二版》金国庆、刘加海、季江民、严冰。
参考文档:
1、标准md语法教程
2、重定向编程 dup和dup2函数