今天这篇文章是最后一篇介绍进程的文章了,这段时间的学习,收获很多,非常感谢网友在看我写的文章,同时提出问题和疑问(这里非常欢迎大家提出问题来,你可以把问题发出到我自己建的交流群或者私聊我,只要我看到了,就会马上回复你的)。在上一遍文章中,我们介绍了守护进程的概念和应用,简单来讲,守护进程你是看不到的,它是默默无闻的为Linux系统服务着,但是我们如果要想和守护进程通信的话怎么办呢?这就是我们今天要讲的话题了。
一、syslog:
1、syslog简介概述:
对于一个从单片机开发转向Linux操作系统中的应用程序开发的攻城狮来说,对程序的调试方法的转换也是非常重要的。单片机的开发,一般使用jlink进行单步调试较多,但是对于在操作系统上进行应用程序一般都是比较庞大的,所以再使用单片机开发的调试思维就不现实啦。最常见的就是使用printf打印与syslog日志打印,对于printf的使用比较简单,所以这里主要介绍一下syslog的用法。
syslog的调试信息的打印是后台运行的,不占用控制台。并且可以进行调试信息级别的控制,这样可以避免输出很多无用的信息,让观看者可以更快的找到希望看到的信息。一般log信息都在操作系统的/var/log/messages这个文件中存储着,但是ubuntu中是在/var/log/syslog文件中的
2、三个函数介绍:
在linux系统中提供了三个函数来实现日志打印输出,我们先用man手册来查看这三个函数(man 3 syslog):
代码语言:javascript复制 SYNOPSIS
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
void vsyslog(int priority, const char *format, va_list ap);
函数的说明:
a、英文注释:
代码语言:javascript复制 DESCRIPTION
openlog():
openlog() opens a connection to the system logger for a program.
The string pointed to by ident is prepended to every message, and is
typically set to the program name. If ident is NULL, the program name
is used. (POSIX.1-2008 does not specify the behavior when ident is
NULL.)
The option argument specifies flags which control the operation of
openlog() and subsequent calls to syslog(). The facility argument
establishes a default to be used if none is specified in subsequent
calls to syslog(). The values that may be specified for option and
facility are described below.
The use of openlog() is optional; it will automatically be called by
syslog() if necessary, in which case ident will default to NULL.
syslog() and vsyslog():
syslog() generates a log message, which will be distributed by sys‐
logd(8).
The priority argument is formed by ORing together a facility value and
a level value (described below). If no facility value is ORed into
priority, then the default value set by openlog() is used, or, if there
was no preceding openlog() call, a default of LOG_USER is employed.
The remaining arguments are a format, as in printf(3), and any argu‐
ments required by the format, except that the two-character sequence %m
will be replaced by the error message string strerror(errno). The for‐
mat string need not include a terminating newline character.
The function vsyslog() performs the same task as syslog() with the dif‐
ference that it takes a set of arguments which have been obtained using
the stdarg(3) variable argument list macros.
closelog():
closelog() closes the file descriptor being used to write to the system
logger. The use of closelog() is optional.
b、中文注释讲解这三个函数:
(1): void openlog(const char *ident, int option, int facility); 用于打开系统记录,它的参数解析如下:
---ident 指向的字符串可以是想要打出的任意字符,它所表示的字符串将固定地加在每行日志的前面以标识这个日志,该标志通常设置为程序的名称。
---option 参数所指定的标志用来控制openlog()操作和syslog()的后续调用。他的值为具体是下列值取或运算的结果,下面是它的详细参数用法:
代码语言:javascript复制Values for option
The option argument to openlog() is a bit mask constructed by ORing
together any of the following values:
LOG_CONS Write directly to the system console if there is an
error while sending to the system logger.
LOG_NDELAY Open the connection immediately (normally, the connec‐
tion is opened when the first message is logged). This
may be useful, for example, if a subsequent chroot(2)
would make the pathname used internally by the logging
facility unreachable.
LOG_NOWAIT Don't wait for child processes that may have been cre‐
ated while logging the message. (The GNU C library does
not create a child process, so this option has no effect
on Linux.)
LOG_ODELAY The converse of LOG_NDELAY; opening of the connection is
delayed until syslog() is called. (This is the default,
and need not be specified.)
LOG_PERROR (Not in POSIX.1-2001 or POSIX.1-2008.) Also log the
message to stderr.
LOG_PID Include the caller's PID with each message.
代码语言:javascript复制LOG_CONS:
直接写入系统控制台,如果有一个错误,同时发送到系统日志记录。
LOG_NDELAY:
立即打开连接(通常,打开连接时记录的第一条消息)。
LOG_NOWAIT:
不要等待子进程,因为其有可能在记录消息的时候就被创建了(GNU C库不创建子进程,所以该选项在Linux上没有影响。)
LOG_ODELAY:
延迟连接的打开直到syslog函数调用。(这是默认情况下,需要没被指定的情况下。)
LOG_PERROR:
(不在SUSv3情况下)同时输出到stderr(标准错误文件)。
LOG_PID:
包括每个消息的PID。
---facility参数是用来指定记录消息程序的类型。它让指定的配置文件,将以不同的方式来处理来自不同方式的消息。它的具体用法如下(下面中文注释,注释了一些常用的用的):
代码语言:javascript复制 The facility argument is used to specify what type of program is log‐
ging the message. This lets the configuration file specify that mes‐
sages from different facilities will be handled differently.
LOG_AUTH security/authorization messages //安全/批准信息
LOG_AUTHPRIV security/authorization messages (private)//(安全/批准信息(私有的))
LOG_CRON clock daemon (cron and at)//时钟守护进程(时间单位)
LOG_DAEMON system daemons without separate facility value(系统守护进程没有不同的设备值)
LOG_FTP ftp daemon
LOG_KERN kernel messages (these can't be generated from user pro‐
cesses)
LOG_LOCAL0 through LOG_LOCAL7
reserved for local use
LOG_LPR line printer subsystem
LOG_MAIL mail subsystem
LOG_NEWS USENET news subsystem
LOG_SYSLOG messages generated internally by syslogd(8)
LOG_USER (default)
generic user-level messages(一般用户极的信息)
LOG_UUCP UUCP subsystem
(2):void syslog(int priority, const char *format, ...) 记录至系统记录
----- priority 指的是调试信息的优先级,跟内核中的printk函数类似。内核中定义的各种级别如下:
代码语言:javascript复制 This determines the importance of the message. The levels are, in
order of decreasing importance:
LOG_EMERG system is unusable
LOG_ALERT action must be taken immediately
LOG_CRIT critical conditions
LOG_ERR error conditions
LOG_WARNING warning conditions
LOG_NOTICE normal, but significant, condition
LOG_INFO informational message
LOG_DEBUG debug-level message
The function setlogmask(3) can be used to restrict logging to specified
levels only.
代码语言:javascript复制LOG_EMERG:紧急情况,需要立即通知技术人员。
LOG_ALERT:应该被立即改正的问题,如系统数据库被破坏,ISP连接丢失。
LOG_CRIT:重要情况,如硬盘错误,备用连接丢失。
LOG_ERR:错误,不是非常紧急,在一定时间内修复即可。
LOG_WARNING:警告信息,不是错误,比如系统磁盘使用了85%等。
LOG_NOTICE:不是错误情况,也不需要立即处理。
LOG_INFO:情报信息,正常的系统消息,比如骚扰报告,带宽数据等,不需要处理。
LOG_DEBUG:包含详细的开发情报的信息,通常只在调试一个程序时使用。
(3): void closelog(void):这个函数就没什么好说的,打开系统记录。
3、syslog的工作原理:
(1)操作系统中有一个守护进程syslogd(开机运行,关机时才结束),这个守护进程syslogd负责进行日志文件的写入和维护。
(2)syslogd是独立于我们任意一个进程而运行的。我们当前进程和syslogd进程本来是没有任何关系的,但是我们当前进程可以通过调用openlog打开一个和syslogd相连接的通道,然后通过syslog向syslogd发消息,然后由syslogd来将其写入到日志文件系统中。
(3)syslogd其实就是一个日志文件系统的服务器进程,提供日志服务。任何需要写日志的进程都可以通过openlog/syslog/closelog这三个函数来利用syslogd提供的日志服务。这就是操作系统的服务式的设计。
4、实战示例:
代码语言:javascript复制 #include <stdio.h>
#include <syslog.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
printf("my pid = %d.n", getpid());
openlog("b.out", LOG_PID | LOG_CONS, LOG_USER);
syslog(LOG_INFO, "this is my log info.%d", 23);
syslog(LOG_INFO, "this is another log info.");
syslog(LOG_INFO, "this is 3th log info.");
closelog();
}
演示效果:
注意:在Ubuntu中日志在/var/log/syslog文件中,不同linux的发行版路径会不一样,其他的日志可能会在/var/log/messages
二、让程序不能多次被执行:
1、问题引入:
(1)因为守护进程是长时间运行而不退出,因此./a.out执行一次就有一个进程,执行多次就有多个进程。这个守护进程,例如在我们的windows系统上打开一个应用程序,它就有一个守护进程如下图所示:
(2)这样并不是我们想要的。我们守护进程一般都是服务器,服务器程序只要运行一个就够了,多次同时运行并没有意义甚至会带来错误。
(3)因此我们希望我们的程序具有一个单例运行的功能。意思就是说当我们./a.out去运行程序时,如果当前还没有这个程序的进程运行则运行之,如果之前已经有一个这个程序的进程在运行则本次运行直接退出(提示程序已经在运行)。
2、解决问题的实现方法:
(1)最常用的一种方法就是:用一个文件的存在与否来做标志。具体做法是程序在执行之初去判断一个特定的文件是否存在,若存在则标明进程已经在运行,若不存在则标明进程没有在运行。然后运行程序时去创建这个文件。当程序结束的时候去删除这个文件即可。下面是代码测试:
代码语言:javascript复制 #include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#define FILE "/var/aston_test_single"
int main(void)
{
// 程序执行之初,先去判断文件是否存在
int fd = -1;
fd = open(FILE, O_RDWR | O_TRUNC | O_CREAT | O_EXCL, 0664);
if (fd < 0)
{
if (errno == EEXIST)//这里的errno可以使用man 3 perror 查看的
{
printf("进程已经存在,并不要重复执行n");
return -1;
}
}
printf("文件打开成功n");
}
演示结果如下:
这里要注意,你在unbuntu系统下要切换到root用下对这个操作才起作用,主要原因我们在open函数时使用了权限0664,普通用户对这个程序测试会失败的,说白了,就是权限不够的原因,所以操作的时候最用户来试验这个程序,最好切换到root用户来试验,如果你用普通用户测试这个程序的话,可能测试不成功:
切换到root用户下就能成功的:
上面那个文件时不存在的,这里在open时使用O_CREAT参数,所以创建了这个文件/var/aston_test_single;接下来为了更加明显的看出效果,我重新把这个文件删除掉,来看看试验现象和结果:
(2)当程序结束的时候,我们想让这个文件自动删除,我们怎样操作呢?下面我们要用到atexit()函数了,有关它的用法这里我就不详细介绍了,可以使用man 手册来查看他的用法的,下面是本实验的代码测试和试验结果现象:
代码语言:javascript复制 #include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#define FILE "/var/aston_test_single"
void delete_file(void);
int main(void)
{
// 程序执行之初,先去判断文件是否存在
int fd = -1;
fd = open(FILE, O_RDWR | O_TRUNC | O_CREAT | O_EXCL, 0664);
if (fd < 0)
{
if (errno == EEXIST)
{
printf("进程已经存在,并不要重复执行n");
return -1;
}
}
atexit(delete_file); // 注册进程清理函数
int i = 0;
for (i=0; i<10; i )
{
printf("I am running...%dn", i);
sleep(1);
}
return 0;
}
void delete_file(void)
{
remove(FILE);
}
接下来我来打开两个窗口来做这个试验,让效果更加明显,不过另外一个窗口你得切换到aston用户下,因为我创建的文件的就是在这个用户下创建的,不然看不到这种效果的:
三、进程间的通信:
1、进程间通信的概念:
每个进程各自有不同的用户地址空间,任何一个进程的变量在另一个进程中都是看不到的,所以进程之间要交换数据必须通过内核,在内核中开辟出一块缓冲区。一个进程把自己的数据从用户空间拷贝到内核缓冲区,另一个进程再从内核缓冲区把数据读走。内核提供的这种机制称为进程间通信(IPC,Inter Process Communication)。
2、进程间通信的目的:
----数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间
----共享数据:多个进程要操作共享数据,一个进程对共享数据
----信息传递:一个进程需要向另一个进程发送消息,通知它发生了某种事件。
----资源共享:多个进程之间共享同样的数据。为了做到这一点,需要内核提供锁和同步机制。
----进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,病能够及时知道它的状态改变
3、进程间通信方式:
(1):管道(pipe):
a、普通管道PIPE:通常有两种限制,一是单工,只能单向传输;二是只能在父子或者兄弟进程间使用.
b、流管道s_pipe: 去除了第一种限制,为半双工,只能在父子或兄弟进程间使用,可以双向传输.
c、命名管道:name_pipe:去除了第二种限制,可以在许多并不相关的进程之间进行通讯.
(2):信号量( semophore ) :
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(3):消息队列( message queue ) :
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(4):信号 ( sinal ) :
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
(5):共享内存( shared memory ) :
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
(6):套接字( socket ) :
套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
四、总结:
今天的分享就到这里了,进程之间通信实战后续会接着分享的。