Shell脚本实现文件的互斥访问



1.前言

Shell中似乎不存在通用的文件互斥访问机制。Linux下有flock命令,但Solaris没有。

通过一番调查,笔者找到了一个简单易行的方法来弥补Shell的这一不足。

 

2.原理

原理比较简单。主要是以下两个命令的配合使用:

命令A:

set -o noclobber

命令B:

echo XXX > filename.lock

 

命令A启用Shell的“禁用重定向覆盖”功能,命令B是重定向操作。

filename.lock不存在时,不考虑访问权限的限制,命令B总是能正常结束。

filename.lock已经存在时,使用了命令A之后的命令B将会异常结束。而不使用命令A时命令B会将原来的filename.lock覆盖掉然后正常结束。

 

要达到互斥的效果,必须具备的条件是,命令B的执行过程中,从判断文件是否存在到生成文件的过程是原子的(CPU不能被其他进程抢占)。否则即使有判断文件是否存在的功能,也不能阻止以下的情况发生。

处理顺序

进程A

进程B

文件状态

1

判断文件是否存在

不存在

2

—(CPU被抢占)

判断文件是否存在

不存在

3

生成文件

—(CPU被抢占)

存在(由A生成)

4

生成文件

存在(由B生成,覆盖A生成的)

 

命令A和命令B的配合使用,能否满足上述的必要条件?

官方文档中找不到相关的说明,于是进行了一番调查。过程如下。

 

3.调查过程

3.1 命令B对应的系统调用

首先考虑命令B执行过程中是否使用了加锁类型的系统调用。

使用truss命令可以查看进程所使用的系统调用。例如:

truss -a -e -f -d -p

或者

#将命令内容写在cmd.sh中

truss -a -e -f -d ./cmd.sh

 

发现命令B最主要的两个系统调用如下

stat64("xxx", xxx)

open64("xxx", O_WRONLY|O_CREAT|O_EXCL, 0666)

stat用于判断文件是否存在,open用于生成文件。

 

3.2 命令A对命令B的影响

命令A如何实现“禁用重定向覆盖”的?在系统调用级别很可能会有答案。

果然,在没有使用命令A时,命令B没有调用stat64。并且,open64的参数发生了变化:

open64("xxx", O_WRONLY|O_CREAT|O_TRUNC, 0666)

以下,针对stat的有无,和open的参数的不同,分别进行调查。

 

3.3 stat和open是否原子操作

stat和open前后并没有加锁/解锁的系统调用,它们两者并没有组合成原子操作。因此,有没有stat,并不影响“互斥”的结果。

通过实验,在两个进程循环冲突2万次左右时,发生了一次同时通过stat验证,并都返回“文件不存在”之后,同时执行open,试图同时创建文件的情况。

这也证明,单纯的判断文件有无不能确保“互斥”效果。

 

3.4 open是否原子操作

上述实验中两个进程同时执行了open,但其中一个进程以”EEXIST”出错退出,另一个则正常结束。

solaris上的“man open”的结果证实了这不是偶然:

     O_EXCL

           If O_CREAT and O_EXCL are set,  open()  fails  if  the

           file  exists.  The check for the existence of the file

           and the creation of the file if it does not  exist  is

           atomic  with  respect  to  other  processes  executing

           ^^^^^^

           open() naming the same filename in the same  directory

           with  O_EXCL  and  O_CREAT set. If O_CREAT is not set,

           the effect is undefined.

写的很明白了。“O_EXCL ”模式的open内部的文件存在性判断和文件生成操作是一组原子操作。多个进程同时调用open创建同一个文件时,可以确保仅有一个进程能成功创建文件。

 

除非shell的内部实现发生变化,一般的软件可以放心使用命令A和命令B的组合来实现互斥了。对于要求极高,不能出任何差错的软件,可以考虑自行开发应用程序,通过“O_EXCL ”模式的open系统调用来实现严格的互斥功能。

 

4.脚本示例

myflock.sh:

#!/bin/bash

######### return code ######################################

# 0 : normal

# 1 : error (invalid option or file not exist)

# 2 : locked by other process (wait 100 ms and exit)

# 3 : fail to unlock the file locked by other process

############################################################

# flag to show debug messeages

DEBUG=1

#########   sub  func   ########

print_usage()

{

    echo "Usage : myflock.sh LOCK|UNLOCK PID"

}

print_debug()

{

    if [ $DEBUG = 1 ]; then

        echo $1

    fi

}

#########   main   ########

if [ $# -ne 3 ]; then

    print_usage

    exit 1

fi

 

case $1 in

    LOCK)

        if [ -f $2 ]; then

            DIRNAME=`dirname $2`

            FILENAME=`basename $2`

            FILENAME="${DIRNAME}/${FILENAME}.lock"

        else

            echo "File doesn’t  exist : $2"

            exit 1

        fi

        # use "set -o noclobber" to control multi creation of file

        set -o noclobber

        echo $3 > ${FILENAME}

        if [ $? -ne 0 ]; then

            CURLOCKPID=`cat ${FILENAME}`

            # check if locked by itself

            if [ $3 = $CURLOCKPID ]; then

                print_debug "debug 1 locked by itself"

                exit 0

            fi

 

            # fail to lock file

            # check if the file is locked by existing process

            print_debug "debug 2 CURLOCKPID=$CURLOCKPID"

            ps -ef |awk '{print $2}' | grep $CURLOCKPID > /dev/null

            if [ $? -ne 0 ]; then

                # process doesn’t  exist. delete the lockfile

                print_debug "debug 3 process $CURLOCKPID doesn’t  exist. delete the lockfile ${FILENAME}"

                rm -f ${FILENAME}

            fi

            # sleep 100 ms

            ./mysleepms 100

            exit 2

        fi   

        exit 0

        ;;

    UNLOCK)

        if [ -f $2 ]; then

            DIRNAME=`dirname $2`

            FILENAME=`basename $2`

            FILENAME="${DIRNAME}/${FILENAME}.lock"

        else

            echo "File doesn’t  exist : $2"

            exit 1

        fi

 

        # check if locked

        if [ -f ${FILENAME} ]; then

            CURLOCKPID=`cat ${FILENAME}`       

            if [ $3 -ne $CURLOCKPID ]; then

                # fail to unlock file which is locked by other user

                echo "Fail to unlock file which is locked by process $CURLOCKPID"

                exit 3

            fi

       

            rm -f ${FILENAME}

            exit 0

        else

            print_debug "debug 4 lockfile doesn't exist : ${FILENAME}"

            exit 0

        fi

        ;;

    *)

        print_usage

        exit 1

        ;;

esac

mysleepms:

// mysleepms   (ms)

 

#include

#include

int main(int argc, char ** argv){

    if(argc < 2){

        usleep(100000);

    }

    else{

        int num=atoi(argv[1]);

        if(num <= 0)return 0;

        usleep(num*1000);

    }

    return 0;

}

 

 

 

你可能感兴趣的:(操作系统,shell,Linux,Solaris,互斥)