Charlie's Angel

2009년 12월 15일 화요일

Debugging Linux Child Process with gdb

Debugging Linux Child Process with gdb

pid = fork();
printf("forked child pid = %d.\n",pid);
printf("Run gdb in seperate terminal window,\n");
printf("and attach child pid within 60 seconds.\n");
sleep(60);

The Analemma

The Analemma


This very unusual photograph was recorded by Dennis di Cicco on a single piece of film that was exposed on 45 different dates throughout an entire year in a permanently mounted camera (44 images were of the sun, with an additional exposure to include the house and tree). It shows the sun's position in the sky at the same time of day on each date. The figure-8 loop of sun images is well known to fanciers of sundials or old terrestrial globes - a graphic representation of the changing seasons and the equation of time.
To see how this figure-8 pattern originates, let's consider how the making of this picture has been affected by the annual apparent motion of the sun along the ecliptic. For simplicity, we'll suppose that all the exposures are made at exactly 8:30 am Eastern standard time.

For the moment, suppose that the sun's annual path around the sky is along the celestial equator instead, and that this motion is at a uniform rate. If that were true, than all 44 solar images in the picture would coincide. Actually, the ecliptic motion is tilted 23.5 degrees to the celestial equator, so that in late June the sun is 23.5 degrees north of the equator and in late December 23.5 degrees south of it. This annual north-south oscillation of the sun's declination angle is responsible for the lengthwise extension of the analemma pattern.

Because the ecliptic is tilted to the equator, the sun's motion relative to the stars is due east only in late June and late December. Hence the sun's eastward advance per day is greatest at those times, and least in March and September when the ecliptic crosses the equator slantingly. This means that when the sun is photographed at 8:30 am on different dates of the year, it generally is some minutes ahead of or behind clock time. The effect of this is to give an east-west spread to the pattern of solar images. We would get a photograph in which the two loops of the figure-8 were equal in size - provided that the sun's motion along the ecliptic were uniform (which would be the case if the earth's orbit were circular). Actually, the earth's orbit is somewhat elliptical, and the effect is to distort the figure-8 to the shape seen in the photograph.

Set the analemma curve upright, and it becomes a miniature almanac. The vertical coordinate of each point on it gives the sun's declination on a particular day of the year, while the horizontal coordinate tells how much the sun is ahead of or behind clock time on that day. (For further explanation of the analemma, see Bernard Oliver's article in Sky and Telescope for July, 1972, page 20.)


The World at Night Project

2009년 12월 14일 월요일

대한민국 3040 노후재테크 독하게 하라


대한민국 3040 노후재테크 독하게 하라

설득의 비밀: EBS 다큐프라임

아웃도어요리


박미란블로그

예스24

Signal을 이용한 interval timer

Signal을 이용한 interval timer

#include
#include
#include
#include
#include

int g_count;

// 아래 함수는 커널이 불러주는 함수다.
// 주기적으로 타이머 시그널이 발생시에 불러준다.
// 그냥 타이머시그널 핸들러라고 부른다.
void periodicTimerHandler (int signum)
{
static int count = 0;

printf ("[%s:%ld:%s]timer expired %d times\n",
__FILE__,(long) __LINE__, __func__, ++count);
g_count++;
}

void initTimer(float standby, float timeout)
{
struct sigaction sa;
struct itimerval timer;
long sec, usec;
long s_sec, s_usec;
if ( standby <= 0 ) {
printf("invalid timeout value %f\n", timeout);
return ;
}
s_sec = (long)standby;
s_usec = (long)((standby - s_sec)*1000000);

if ( timeout <= 0 ) {
printf("invalid timeout value %f\n", timeout);
return ;
}
sec = (long)timeout;
usec = (long)((timeout - sec)*1000000);
// timer 핸들러를 등록 SIGALRM.
memset (&sa, 0, sizeof (sa));
sa.sa_handler = &periodicTimerHandler;

sigaction (SIGALRM, &sa, NULL);

// 타이머 시작시기 stand by....
timer.it_value.tv_sec = s_sec;
timer.it_value.tv_usec = s_usec;

// 주기적으로 동작
timer.it_interval.tv_sec = sec;
timer.it_interval.tv_usec = usec;
setitimer (ITIMER_REAL, &timer, NULL);
printf(" %f 초 후부터, 매 %f초 간격으로 타이머가 동작합니다\n", standby, timeout);
}

int main ()
{

char buf[1024];
int ret;

// 출력이 printf()와 write()를 둘다 사용해서리, 출력이 뒤섞이게 됨으로
// stdout을 버퍼링없이 사용.
// 즉, printf()와 write(1,.....)이 거의 동급으로 동작
setvbuf (stdout, NULL, _IONBF, 0);

// 타이머는 10초 후부터 매 0.5초 간격으로 발생
initTimer( 10,0.5 );

/* Do busy work. */
while (1) {

ret = read(0, buf, 1024); // 보통 입력이 있거나,
// 0번 장치(입력파일)에 문제시 함수리턴
if ( ret <= 0 ) {
if (errno == EINTR ) { // 그러나, 임의의 signal이 뜨면 리턴함.
// read()만이 아니고, 모든 블록킹 시스템호출이
// 그렇게 동작함
printf("시그널이 떠서 read()에서 탈출... 그래서 재시도..\n");
continue;
}
else {
printf("read() error\n");
exit(0);
}
}
else {
printf("global count = %ld\n", g_count);
printf("read() return %d\nval = [", ret);
write(1, buf, ret);
printf("]\n", ret);
}
}
}

2009년 12월 13일 일요일

Changing File Attributes with Mfc

Changing File Attributes with Mfc

SteinSOFT.net
Changing File Attributes with Mfc
http://www.steinsoft.net/
If you intend to change the filename of a file or simply to modify the creation date then you're right here. Indeed doing so isn't that difficult under Windows. But at first you have to add the following enum to your code that lists all possible fileattributes (stdafx.h is okay):


enum Attribute
{
normal = 0x00, //a normal file
readOnly = 0x01, //read-only, can't write into the file
hidden = 0x02, //likes to hide..
sys = 0x04, //system file
volume = 0x08, //seems to be a harddisk after MSDN help
directory = 0x10, //guess..
archive = 0x20 //don't know what Windows means by that..
};


To get the attributes of a file, you have to use CFile::GetStatus(..). The good thing about this function is, that there's both a version that can be called on an exisiting CFile object and a static version that can be used without creating a CFile object.


class CFile
{
// ...
BOOL GetStatus( CFileStatus& rStatus ) const;
static BOOL PASCAL GetStatus( LPCTSTR lpszFileName, CFileStatus& rStatus );
//...
};

//file information struct
struct CFileStatus
{
CTime m_ctime; //date of creation
CTime m_mtime; //date when last modified
CTime m_atime; //date when last accessed
LONG m_size; //size in bytes (1 KByte = 1024 bytes)
BYTE m_attribute; //OR'd attributes (see above)
char m_szFullName[_MAX_PATH]; //the filename itself
};


The CFileStatus contains all necessary - and perhaps unnecessary information - about a file. After saving the data into a CFileStatus struct using CFile::GetStatus(..), this data can be modified and saved with CFile::SetStatus(..) later on which works exactely like GetStatus(..).

Note: when changing the time stamps of a file, the m_mtime - last modfied date - field has to be changed too otherwise Windows won't know that something has been changed. This isn't necessary when changing the file attributes. Last but not least here's a little code example:


//misc. file
CString filename = "test.dat";
CFileStatus fileStatus;

//read information
CFile::GetStatus(filename,fileStatus);

//change last access date
CTime lastAccess = fileStatus.m_atime;
//modify (new date/time : 10:10:10, 1989-12-12)
lastAccess = CTime(1989,12,12,10,10,10);

//m_mtime must always be changed
fileStatus.m_mtime = lastAccess;
//change access date too and noone will notice anything ;-)
fileStatus.m_atime = lastAccess;
//as a little gimmick, we'll change the file attributes too
fileStatus.m_attribute = readOnly | hidden;

//save
CFile::SetStatus(filename,fileStatus);


This method has also been used in FileEditor so if you want to see a real life example, check out the source code. But I think everything should be clear by now and if it's not so, visit the message board. Happy Coding!

Copyright © by SteinSOFT

Linux Shared Libraries Program Library HOWTO

Linux Shared Libraries Program Library HOWTO

3. Shared Libraries
Shared libraries are libraries that are loaded by programs when they start. When a shared library is installed properly, all programs that start afterwards automatically use the new shared library. It's actually much more flexible and sophisticated than this, because the approach used by Linux permits you to:


update libraries and still support programs that want to use older, non-backward-compatible versions of those libraries;

override specific libraries or even specific functions in a library when executing a particular program.

do all this while programs are running using existing libraries.


3.1. Conventions
For shared libraries to support all of these desired properties, a number of conventions and guidelines must be followed. You need to understand the difference between a library's names, in particular its ``soname'' and ``real name'' (and how they interact). You also need to understand where they should be placed in the filesystem.

3.1.1. Shared Library Names
Every shared library has a special name called the ``soname''. The soname has the prefix ``lib'', the name of the library, the phrase ``.so'', followed by a period and a version number that is incremented whenever the interface changes (as a special exception, the lowest-level C libraries don't start with ``lib''). A fully-qualified soname includes as a prefix the directory it's in; on a working system a fully-qualified soname is simply a symbolic link to the shared library's ``real name''.

Every shared library also has a ``real name'', which is the filename containing the actual library code. The real name adds to the soname a period, a minor number, another period, and the release number. The last period and release number are optional. The minor number and release number support configuration control by letting you know exactly what version(s) of the library are installed. Note that these numbers might not be the same as the numbers used to describe the library in documentation, although that does make things easier.

In addition, there's the name that the compiler uses when requesting a library, (I'll call it the ``linker name''), which is simply the soname without any version number.

The key to managing shared libraries is the separation of these names. Programs, when they internally list the shared libraries they need, should only list the soname they need. Conversely, when you create a shared library, you only create the library with a specific filename (with more detailed version information). When you install a new version of a library, you install it in one of a few special directories and then run the program ldconfig(8). ldconfig examines the existing files and creates the sonames as symbolic links to the real names, as well as setting up the cache file /etc/ld.so.cache (described in a moment).

ldconfig doesn't set up the linker names; typically this is done during library installation, and the linker name is simply created as a symbolic link to the ``latest'' soname or the latest real name. I would recommend having the linker name be a symbolic link to the soname, since in most cases if you update the library you'd like to automatically use it when linking. I asked H. J. Lu why ldconfig doesn't automatically set up the linker names. His explanation was basically that you might want to run code using the latest version of a library, but might instead want development to link against an old (possibly incompatible) library. Therefore, ldconfig makes no assumptions about what you want programs to link to, so installers must specifically modify symbolic links to update what the linker will use for a library.

Thus, /usr/lib/libreadline.so.3 is a fully-qualified soname, which ldconfig would set to be a symbolic link to some realname like /usr/lib/libreadline.so.3.0. There should also be a linker name, /usr/lib/libreadline.so which could be a symbolic link referring to /usr/lib/libreadline.so.3.

3.1.2. Filesystem Placement
Shared libraries must be placed somewhere in the filesystem. Most open source software tends to follow the GNU standards; for more information see the info file documentation at info:standards#Directory_Variables. The GNU standards recommend installing by default all libraries in /usr/local/lib when distributing source code (and all commands should go into /usr/local/bin). They also define the convention for overriding these defaults and for invoking the installation routines.

The Filesystem Hierarchy Standard (FHS) discusses what should go where in a distribution (see http://www.pathname.com/fhs). According to the FHS, most libraries should be installed in /usr/lib, but libraries required for startup should be in /lib and libraries that are not part of the system should be in /usr/local/lib.

There isn't really a conflict between these two documents; the GNU standards recommend the default for developers of source code, while the FHS recommends the default for distributors (who selectively override the source code defaults, usually via the system's package management system). In practice this works nicely: the ``latest'' (possibly buggy!) source code that you download automatically installs itself in the ``local'' directory (/usr/local), and once that code has matured the package managers can trivially override the default to place the code in the standard place for distributions. Note that if your library calls programs that can only be called via libraries, you should place those programs in /usr/local/libexec (which becomes /usr/libexec in a distribution). One complication is that Red Hat-derived systems don't include /usr/local/lib by default in their search for libraries; see the discussion below about /etc/ld.so.conf. Other standard library locations include /usr/X11R6/lib for X-windows. Note that /lib/security is used for PAM modules, but those are usually loaded as DL libraries (also discussed below).

3.2. How Libraries are Used
On GNU glibc-based systems, including all Linux systems, starting up an ELF binary executable automatically causes the program loader to be loaded and run. On Linux systems, this loader is named /lib/ld-linux.so.X (where X is a version number). This loader, in turn, finds and loads all other shared libraries used by the program.

The list of directories to be searched is stored in the file /etc/ld.so.conf. Many Red Hat-derived distributions don't normally include /usr/local/lib in the file /etc/ld.so.conf. I consider this a bug, and adding /usr/local/lib to /etc/ld.so.conf is a common ``fix'' required to run many programs on Red Hat-derived systems.

If you want to just override a few functions in a library, but keep the rest of the library, you can enter the names of overriding libraries (.o files) in /etc/ld.so.preload; these ``preloading'' libraries will take precedence over the standard set. This preloading file is typically used for emergency patches; a distribution usually won't include such a file when delivered.

Searching all of these directories at program start-up would be grossly inefficient, so a caching arrangement is actually used. The program ldconfig(8) by default reads in the file /etc/ld.so.conf, sets up the appropriate symbolic links in the dynamic link directories (so they'll follow the standard conventions), and then writes a cache to /etc/ld.so.cache that's then used by other programs. This greatly speeds up access to libraries. The implication is that ldconfig must be run whenever a DLL is added, when a DLL is removed, or when the set of DLL directories changes; running ldconfig is often one of the steps performed by package managers when installing a library. On start-up, then, the dynamic loader actually uses the file /etc/ld.so.cache and then loads the libraries it needs.

By the way, FreeBSD uses slightly different filenames for this cache. In FreeBSD, the ELF cache is /var/run/ld-elf.so.hints and the a.out cache is /var/run/ld.so.hints. These are still updated by ldconfig(8), so this difference in location should only matter in a few exotic situations.

3.3. Environment Variables
Various environment variables can control this process, and there are environment variables that permit you to override this process.

3.3.1. LD_LIBRARY_PATH
You can temporarily substitute a different library for this particular execution. In Linux, the environment variable LD_LIBRARY_PATH is a colon-separated set of directories where libraries should be searched for first, before the standard set of directories; this is useful when debugging a new library or using a nonstandard library for special purposes. The environment variable LD_PRELOAD lists shared libraries with functions that override the standard set, just as /etc/ld.so.preload does. These are implemented by the loader /lib/ld-linux.so. I should note that, while LD_LIBRARY_PATH works on many Unix-like systems, it doesn't work on all; for example, this functionality is available on HP-UX but as the environment variable SHLIB_PATH, and on AIX this functionality is through the variable LIBPATH (with the same syntax, a colon-separated list).

LD_LIBRARY_PATH is handy for development and testing, but shouldn't be modified by an installation process for normal use by normal users; see ``Why LD_LIBRARY_PATH is Bad'' at http://www.visi.com/~barr/ldpath.html for an explanation of why. But it's still useful for development or testing, and for working around problems that can't be worked around otherwise. If you don't want to set the LD_LIBRARY_PATH environment variable, on Linux you can even invoke the program loader directly and pass it arguments. For example, the following will use the given PATH instead of the content of the environment variable LD_LIBRARY_PATH, and run the given executable: /lib/ld-linux.so.2 --library-path PATH EXECUTABLE

Just executing ld-linux.so without arguments will give you more help on using this, but again, don't use this for normal use - these are all intended for debugging.

3.3.2. LD_DEBUG
Another useful environment variable in the GNU C loader is LD_DEBUG. This triggers the dl* functions so that they give quite verbose information on what they are doing. For example: export LD_DEBUG=files
command_to_run

displays the processing of files and libraries when handling libraries, telling you what dependencies are detected and which SOs are loaded in what order. Setting LD_DEBUG to ``bindings'' displays information about symbol binding, setting it to ``libs'' displays the library search paths, and setting ti to ``versions'' displays the version depdendencies.

Setting LD_DEBUG to ``help'' and then trying to run a program will list the possible options. Again, LD_DEBUG isn't intended for normal use, but it can be handy when debugging and testing.

3.3.3. Other Environment Variables
There are actually a number of other environment variables that control the loading process; their names begin with LD_ or RTLD_. Most of the others are for low-level debugging of the loader process or for implementing specialized capabilities. Most of them aren't well-documented; if you need to know about them, the best way to learn about them is to read the source code of the loader (part of gcc).

Permitting user control over dynamically linked libraries would be disastrous for setuid/setgid programs if special measures weren't taken. Therefore, in the GNU loader (which loads the rest of the program on program start-up), if the program is setuid or setgid these variables (and other similar variables) are ignored or greatly limited in what they can do. The loader determines if a program is setuid or setgid by checking the program's credentials; if the uid and euid differ, or the gid and the egid differ, the loader presumes the program is setuid/setgid (or descended from one) and therefore greatly limits its abilities to control linking. If you read the GNU glibc library source code, you can see this; see especially the files elf/rtld.c and sysdeps/generic/dl-sysdep.c. This means that if you cause the uid and gid to equal the euid and egid, and then call a program, these variables will have full effect. Other Unix-like systems handle the situation differently but for the same reason: a setuid/setgid program should not be unduly affected by the environment variables set.

3.4. Creating a Shared Library
Creating a shared library is easy. First, create the object files that will go into the shared library using the gcc -fPIC or -fpic flag. The -fPIC and -fpic options enable ``position independent code'' generation, a requirement for shared libraries; see below for the differences. You pass the soname using the -Wl gcc option. The -Wl option passes options along to the linker (in this case the -soname linker option) - the commas after -Wl are not a typo, and you must not include unescaped whitespace in the option. Then create the shared library using this format:

gcc -shared -Wl,-soname,your_soname \
-o library_name file_list library_list


Here's an example, which creates two object files (a.o and b.o) and then creates a shared library that contains both of them. Note that this compilation includes debugging information (-g) and will generate warnings (-Wall), which aren't required for shared libraries but are recommended. The compilation generates object files (using -c), and includes the required -fPIC option:

gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -shared -Wl,-soname,libmystuff.so.1 \
-o libmystuff.so.1.0.1 a.o b.o -lc


Here are a few points worth noting:


Don't strip the resulting library, and don't use the compiler option -fomit-frame-pointer unless you really have to. The resulting library will work, but these actions make debuggers mostly useless.

Use -fPIC or -fpic to generate code. Whether to use -fPIC or -fpic to generate code is target-dependent. The -fPIC choice always works, but may produce larger code than -fpic (mnenomic to remember this is that PIC is in a larger case, so it may produce larger amounts of code). Using -fpic option usually generates smaller and faster code, but will have platform-dependent limitations, such as the number of globally visible symbols or the size of the code. The linker will tell you whether it fits when you create the shared library. When in doubt, I choose -fPIC, because it always works.

In some cases, the call to gcc to create the object file will also need to include the option ``-Wl,-export-dynamic''. Normally, the dynamic symbol table contains only symbols which are used by a dynamic object. This option (when creating an ELF file) adds all symbols to the dynamic symbol table (see ld(1) for more information). You need to use this option when there are 'reverse dependencies', i.e., a DL library has unresolved symbols that by convention must be defined in the programs that intend to load these libraries. For ``reverse dependencies'' to work, the master program must make its symbols dynamically available. Note that you could say ``-rdynamic'' instead of ``-Wl,export-dynamic'' if you only work with Linux systems, but according to the ELF documentation the ``-rdynamic'' flag doesn't always work for gcc on non-Linux systems.


During development, there's the potential problem of modifying a library that's also used by many other programs -- and you don't want the other programs to use the ``developmental''library, only a particular application that you're testing against it. One link option you might use is ld's ``rpath'' option, which specifies the runtime library search path of that particular program being compiled. From gcc, you can invoke the rpath option by specifying it this way: -Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH)

If you use this option when building the library client program, you don't need to bother with LD_LIBRARY_PATH (described next) other than to ensure it's not conflicting, or using other techniques to hide the library.

3.5. Installing and Using a Shared Library
Once you've created a shared library, you'll want to install it. The simple approach is simply to copy the library into one of the standard directories (e.g., /usr/lib) and run ldconfig(8).

First, you'll need to create the shared libraries somewhere. Then, you'll need to set up the necessary symbolic links, in particular a link from a soname to the real name (as well as from a versionless soname, that is, a soname that ends in ``.so'' for users who don't specify a version at all). The simplest approach is to run: ldconfig -n directory_with_shared_libraries



Finally, when you compile your programs, you'll need to tell the linker about any static and shared libraries that you're using. Use the -l and -L options for this.

If you can't or don't want to install a library in a standard place (e.g., you don't have the right to modify /usr/lib), then you'll need to change your approach. In that case, you'll need to install it somewhere, and then give your program enough information so the program can find the library... and there are several ways to do that. You can use gcc's -L flag in simple cases. You can use the ``rpath'' approach (described above), particularly if you only have a specific program to use the library being placed in a ``non-standard'' place. You can also use environment variables to control things. In particular, you can set LD_LIBRARY_PATH, which is a colon-separated list of directories in which to search for shared libraries before the usual places. If you're using bash, you could invoke my_program this way using:

LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program


If you want to override just a few selected functions, you can do this by creating an overriding object file and setting LD_PRELOAD; the functions in this object file will override just those functions (leaving others as they were).

Usually you can update libraries without concern; if there was an API change, the library creator is supposed to change the soname. That way, multiple libraries can be on a single system, and the right one is selected for each program. However, if a program breaks on an update to a library that kept the same soname, you can force it to use the older library version by copying the old library back somewhere, renaming the program (say to the old name plus ``.orig''), and then create a small ``wrapper'' script that resets the library to use and calls the real (renamed) program. You could place the old library in its own special area, if you like, though the numbering conventions do permit multiple versions to live in the same directory. The wrapper script could look something like this: #!/bin/sh
export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH
exec /usr/bin/my_program.orig $*

Please don't depend on this when you write your own programs; try to make sure that your libraries are either backwards-compatible or that you've incremented the version number in the soname every time you make an incompatible change. This is just an ``emergency'' approach to deal with worst-case problems.

You can see the list of the shared libraries used by a program using ldd(1). So, for example, you can see the shared libraries used by ls by typing: ldd /bin/ls

Generally you'll see a list of the sonames being depended on, along with the directory that those names resolve to. In practically all cases you'll have at least two dependencies:


/lib/ld-linux.so.N (where N is 1 or more, usually at least 2). This is the library that loads all other libraries.

libc.so.N (where N is 6 or more). This is the C library. Even other languages tend to use the C library (at least to implement their own libraries), so most programs at least include this one.

Beware: do not run ldd on a program you don't trust. As is clearly stated in the ldd(1) manual, ldd works by (in certain cases) by setting a special environment variable (for ELF objects, LD_TRACE_LOADED_OBJECTS) and then executing the program. It may be possible for an untrusted program to force the ldd user to run arbitrary code (instead of simply showing the ldd information). So, for safety's sake, don't use ldd on programs you don't trust to execute.

3.6. Incompatible Libraries
When a new version of a library is binary-incompatible with the old one the soname needs to change. In C, there are four basic reasons that a library would cease to be binary compatible:


The behavior of a function changes so that it no longer meets its original specification,

Exported data items change (exception: adding optional items to the ends of structures is okay, as long as those structures are only allocated within the library).

An exported function is removed.

The interface of an exported function changes.


If you can avoid these reasons, you can keep your libraries binary-compatible. Said another way, you can keep your Application Binary Interface (ABI) compatible if you avoid such changes. For example, you might want to add new functions but not delete the old ones. You can add items to structures but only if you can make sure that old programs won't be sensitive to such changes by adding items only to the end of the structure, only allowing the library (and not the application) to allocate the structure, making the extra items optional (or having the library fill them in), and so on. Watch out - you probably can't expand structures if users are using them in arrays.

For C++ (and other languages supporting compiled-in templates and/or compiled dispatched methods), the situation is trickier. All of the above issues apply, plus many more issues. The reason is that some information is implemented ``under the covers'' in the compiled code, resulting in dependencies that may not be obvious if you don't know how C++ is typically implemented. Strictly speaking, they aren't ``new'' issues, it's just that compiled C++ code invokes them in ways that may be surprising to you. The following is a (probably incomplete) list of things that you cannot do in C++ and retain binary compatibility, as reported by Troll Tech's Technical FAQ:


add reimplementations of virtual functions (unless it it safe for older binaries to call the original implementation), because the compiler evaluates SuperClass::virtualFunction() calls at compile-time (not link-time).

add or remove virtual member functions, because this would change the size and layout of the vtbl of every subclass.

change the type of any data members or move any data members that can be accessed via inline member functions.

change the class hierarchy, except to add new leaves.

add or remove private data members, because this would change the size and layout of every subclass.

remove public or protected member functions unless they are inline.

make a public or protected member function inline.

change what an inline function does, unless the old version continues working.

change the access rights (i.e. public, protected or private) of a member function in a portable program, because some compilers mangle the access rights into the function name.


Given this lengthy list, developers of C++ libraries in particular must plan for more than occasional updates that break binary compatibility. Fortunately, on Unix-like systems (including Linux) you can have multiple versions of a library loaded at the same time, so while there is some disk space loss, users can still run ``old'' programs needing old libraries.
윗몸일으키기

장수 위한 20대 필수 식품

장수 위한 20대 필수 식품

[앵커멘트]

영국 리즈대 연구팀이 '장수를 위한 필수 식품 20가지'를 소개했습니다.

폴리페놀이 풍부한 과일, 채소와 함께 커피, 차, 초콜릿 등이 꼽혔다고 의학포털 코메디 닷컴이 보도했습니다.

먼저 사과는 비타민 C와 나트륨, 칼슘, 섬유질이 풍부해 장을 깨끗이 하고 소화를 도와주며 철분 흡수율도 높여 줍니다.

블랙베리와 블루베리에 함유된 안토시아닌은 만성질환이나, 암, 노화 등을 촉진시키는 활성산소를 없애는 역할을 합니다.

홍차에는 카페인, 섬유소, 비타민, 무기질이 들어있어 신진대사를 활발하게 하고 혈액 속의 지방을 제거해 줍니다.

브로콜리는 혈압을 조절하는 칼륨이 풍부해 심장병 위험이 감소합니다.

밀은 칼슘과 인, 철분, 비타민B, 토코페롤이 함유돼 있어 피부 노화를 방지합니다.

체리와 산딸기는 항암, 항산화 효과가 있고 혈액 내 요산을 감소시켜 심장병 위험을 줄입니다.

방울토마토에 들어있는 리코펜 성분은 항암 작용을 합니다.

커피의 폴리페놀 성분은 심장 질환과 뇌경색 등을 예방해 줍니다.

크랜베리 속에 든 플라보노이드와 안토시아닌은 산화를 방지하고, 동맥 경화 위험도 낮춰줍니다.

다크초콜릿은 심혈관 질환과 암을 예방합니다.

녹차는 암을 예방하며 노화를 촉진하는 과산화지질 생성을 억제하고 녹차에 든 엽록소, 섬유소 등은 돌연변이 억제 효과가 있습니다.

오렌지는 섬유질과 비타민C가 풍부하며, 복숭아는 단백질과 아미노산 성분이 풍부해 장 활동에 큰 도움을 줍니다.

자두에는 폴리페놀과 비타민 A가 풍부해 활성산소를 억제하고 피부 미용에 좋습니다.

포도는 장 운동을 촉진시킬 뿐만 아니라 해독 작용까지 합니다.

양파는 혈액 속의 불필요한 지방과 콜레스테롤을 녹여 없애 협심증, 심근경색, 뇌경색, 뇌중풍 등을 예방합니다.

항암 효과가 있는 시금치에는 베타카로틴, 엽산, 비타민, 섬유질 등이 함유돼 있습니다.

딸기에는 엘라직산이 풍부해 발암 물질의 독성으로부터 세포들을 보호합니다.

2009년 12월 12일 토요일

An Interactive shared memory manipulator

An Interactive shared memory manipulator

리눅스 프로그래머를 위한 가이드
--------------------------------------------------------------------------------

shmtool:상호작용의 공유 메모리 조종자 (An Interactive shared memory manipulator)

--------------------------------------------------------------------------------

백그라운드 (Background)
명령어 라인 문법 (Command Line Syntax)
세그먼트에 문자열 쓰기 (Writing strings to the segment)
세그먼트로 부터 문자열 조회하기 (Retrieving strings from the segment)
허가사항 바꾸기 (Changing the Permissions(mode))
세그먼트 지우기 (Deleting the segment)
예제 (Examples)
소스 (The Source)


--------------------------------------------------------------------------------

백그라운드 (Background)
시스템 V IPC 객체들의 마지막 예제는 공유 메모리 세그먼트를 만들고, 읽고, 쓰고, 지우기 위한 명령어 라인 툴인 shmtool이다. 다시 한번, 이전의 예제들처럼, 세그먼트는 이전에 존재하고 있지 않면, 어떤 동작을 수행하는 동안에 만들어 진다.


--------------------------------------------------------------------------------

명령어 라인 문법 (Command Line Syntax)


--------------------------------------------------------------------------------

세그먼트에 문자열 쓰기 (Writing strings to the segment)
세그먼트로 부터 문자열 조회하기 (Retrieving strings from the segment)
허가사항 바꾸기 (Changing the Permissions(mode))
세그먼트 지우기 (Deleting the segment)


--------------------------------------------------------------------------------

세그먼트에 문자열 쓰기 (Writing strings to the segment)
shmtool w "text"


--------------------------------------------------------------------------------

세그먼트로 부터 문자열 조회하기 (Retrieving strings from the segment)
shmtool r


--------------------------------------------------------------------------------

허가사항 바꾸기 (Changing the Permissions(mode))
shmtool m (mode)


--------------------------------------------------------------------------------

세그먼트 지우기 (Deleting the segment)
shmtool d


--------------------------------------------------------------------------------

예제 (Examples)
shmtool w test
shmtool w "This is a test"
shmtool r
shmtool d
shmtool m 660



--------------------------------------------------------------------------------

소스 (The Source)
#include
#include
#include
#include

#define SEGSIZE 100

main(int argc, char *argv[])
{
key_t key;
int shmid, cntr;
char *segptr;

if(argc == 1)
usage();

/* ftok() 호출을 경유하여 유일한 키값을 만든다 */
key = ftok(".", 'S');

/* 공유 메모리 세그먼트를 연다 - 필요하면 만든다 */
if((shmid = shmget(key, SEGSIZE, IPC_CREAT|IPC_EXCL|0666)) == -1)
{
printf("Shared memory segment exists - opening as client\n");

/* Segment probably already exists - try as a client */
if((shmid = shmget(key, SEGSIZE, 0)) == -1)
{
perror("shmget");
exit(1);
}
}
else
{
printf("Creating new shared memory segment\n");
}

/* 현재 프로세스에 공유 메모리 세그먼트를 연결한다 */
if((segptr = shmat(shmid, 0, 0)) == -1)
{
perror("shmat");
exit(1);
}

switch(tolower(argv[1][0]))
{
case 'w': writeshm(shmid, segptr, argv[2]);
break;
case 'r': readshm(shmid, segptr);
break;
case 'd': removeshm(shmid);
break;
case 'm': changemode(shmid, argv[2]);
break;
default: usage();

}
}

writeshm(int shmid, char *segptr, char *text)
{
strcpy(segptr, text);
printf("Done...\n");
}

readshm(int shmid, char *segptr)
{
printf("segptr: %s\n", segptr);
}

removeshm(int shmid)
{
shmctl(shmid, IPC_RMID, 0);
printf("Shared memory segment marked for deletion\n");
}

changemode(int shmid, char *mode)
{
struct shmid_ds myshmds;

/* 내무 자료 구조체로부터 현재 값을 얻는다 */
shmctl(shmid, IPC_STAT, &myshmds);

/* 과거의 허가사항을 표시한다 */
printf("Old permissions were: %o\n", myshmds.shm_perm.mode);

/* 모드를 수정하고 적재한다 */
sscanf(mode, "%o", &myshmds.shm_perm.mode);

/* 모드를 업데이트한다 */
shmctl(shmid, IPC_SET, &myshmds);

printf("New permissions are : %o\n", myshmds.shm_perm.mode);
}

usage()
{
fprintf(stderr, "shmtool - A utility for tinkering with shared memory\n");
fprintf(stderr, "\nUSAGE: shmtool (w)rite \n");
fprintf(stderr, " (r)ead\n");
fprintf(stderr, " (d)elete\n");
fprintf(stderr, " (m)ode change \n");
exit(1);
}



Copyright (c) 1996,1997 by Euibeom.Hwang & SangEun.Oh All Rights Reserved

Email To:Webmaster , Another address
LAST UPDATE Nov 28,1997
Created Nov 28,1997

Shared Memory

=========================================
a.c
=========================================
#include
#include
#include
#include
#define SEGSIZE 100
static key_t shared_key;
static char shared_str[100];
main(int argc, char *argv[])
{
shared_key = 7777777;
int shmid, cntr;
char *segptr;
/* Open the shared memory segment - create if necessary */
if((shmid = shmget(shared_key, SEGSIZE, IPC_CREAT|IPC_EXCL|0666)) == -1)
{
printf("Shared memory segment exists - opening as client ");
/* Segment probably already exists - try as a client */
if((shmid = shmget(shared_key, SEGSIZE, 0)) == -1)
{
perror("shmget");
exit(1);
}
}
else
{
printf("Creating new shared memory segment ");
printf("Key value is : %d ", shared_key);
}
/* Attach (map) the shared memory segment into the current process */
if((segptr = shmat(shmid, 0, 0)) == -1)
{
perror("shmat");
exit(1);
}
////////////////////////
// 정보를 저장한다.

// writing the data into the shared memory
// time/filename/..
// ex) "<0103/01.mp3/01.mp3*02.mp3*03.mp3>"
strcpy(shared_str,"<0103/01.mp3/01.mp3*02.mp3*03.mp3>");
strcpy(segptr, shared_str);
}


======================================================================================
b.c
======================================================================================
#include
#include
#include
#include
#define SEGSIZE 100
static key_t shared_key;
static char shared_str[100];
main(int argc, char *argv[])
{
shared_key = 7777777;
int shmid, cntr;
char *segptr;
/* Open the shared memory segment - create if necessary */
if((shmid = shmget(shared_key, SEGSIZE, IPC_CREAT|IPC_EXCL|0666)) == -1)
{
printf("Shared memory segment exists - opening as client ");
/* Segment probably already exists - try as a client */
if((shmid = shmget(shared_key, SEGSIZE, 0)) == -1)
{
perror("shmget");
exit(1);
}
}
else
{
printf("Creating new shared memory segment ");
printf("Key value is : %d ", shared_key);
}
/* Attach (map) the shared memory segment into the current process */
if((segptr = shmat(shmid, 0, 0)) == -1)
{
perror("shmat");
exit(1);
}
////////////////////////
// 정보를 읽어온다.

printf("%s ",segptr);
}

msgget() 메시지 큐 생성

msgget() 메시지 큐 생성

msgget() 메시지 큐 생성장길석http://forum.falinux.com/zbxe/?document_srl=4201472008.01.04 11:10:16 (*.33.84.154) 3470IPC 관련 함수설명

메시지 큐는 IPC 방법 중에 하나로 자료를 다른 프로세스로 전송할 수 있습니다. 전송되는 자료도 큐의 용량이 허용하는 한, 상대편이 가져가지 않는다고 하더라도 계속 전송할 수 있으며, 나중에 다른 프로세스가 큐가 비워질 때까지 계속 읽어 들일 수 있습니다.

또한 메시지 큐는 전송한는 자료를 커널이 간직하기 때문에 전송한 프로세스가 종료되었다고 하더라고 자료가 사라지지 않습니다. 즉, 전송 프로세스가 데이터를 전송한 후 종료해도, 나중에 다른 프로세스가 메시지 큐의 데이터를 가져 올 수 있습니다.

또한 전송되는 큐의 자료는 순자적으로 가져 갈 수도 있지만 데이터 타입에 따라 원하는 자료만 가져 갈 수 있습니다. 즉, 메시지 큐에 전송되는 데이터 구조는 아래와 같습니다.

struct {
long data_type;
char data_buff[BUFF_SIZE];
}
또는,

struct {
long data_type;
int data_num;
char data_buff[BUFF_SIZE];
}
또는,

struct {
long data_type;
int data_num;
}
이런 식으로 구성할 수 있습니다. 즉, 데이터 타입을 나타내는 long 값을 첫번째로 놓는다면 이후의 구조는 자유롭게 구성할 수 있다는 것입니다.

그리고 나중에 자료를 수신하는 쪽은 data_type 값 중에 특정 값으로 설정된 자료만 뽑아서 가져갈 수 있으면, 가져간 자료는 당연이 메시지 큐에서 제거됩니다.

예제에서는 data_type 2 인 것만 뽑아 가다가 나중에는 모든 데이터를 가져가는 방법을 보여 주고 있습니다.

msgget()은 이와 같은 메시지 큐를 생성합니다.

int msgget ( key_t key, int msgflg )

key_t key는 다른 큐와 구별하기 위한 번호입니다. 큐에 대한 번호를 알고 있다면 알고 있는 프로세스끼리 같은 번호의 큐를 사용할 수 있습니다.

int msgflg는 메시지 큐를 만들기 위한 옵션으로 아래와 같은 값을 사용할 수 있습니다.

msgflg 의미
IPC_CREAT key에 해당하는 큐가 있다면 큐의 식별자를 반환하며, 없으면 생성합니다.
IPC_EXCL key에 해당하는 큐가 없다면 생성하지만 있다면 -1을 반환하고 복귀합니다.



헤더 sys/types.h, sys/ipc.h, sys/msg.h
형태 int msgget ( key_t key, int msgflg )
인수 key_t key 시스템에서 다른 큐와 구별되는 번호
int msgflg 옵션

반환 -1 != 메시지 큐 식별자
-1 실패


예제

////////////////////////// main_sender.c
#include
#include
#include
#include
#include
#include
#include

#define BUFF_SIZE 1024

typedef struct {
long data_type;
int data_num;
char data_buff[BUFF_SIZE];
} t_data;

int main( void)
{
int msqid;
int ndx = 0;
t_data data;

if ( -1 == ( msqid = msgget( (key_t)1234, IPC_CREAT ¦ 0666)))
{
perror( "msgget() 실패");
exit( 1);
}

while( 1 )
{
data.data_type = ( ndx++ % 3) +1; // data_type 는 1, 2, 3
data.data_num = ndx;
sprintf( data.data_buff, "type=%d, ndx=%d, http://forum.falinux.com", data.data_type, ndx);

if ( -1 == msgsnd( msqid, &data, sizeof( t_data) - sizeof( long), 0))
{
perror( "msgsnd() 실패");
exit( 1);
}
sleep( 1);
}
}
////////////////////////// main_receiver.c
#include
#include
#include
#include
#include
#include

#define BUFF_SIZE 1024

typedef struct {
long data_type;
int data_num;
char data_buff[BUFF_SIZE];
} t_data;

int main( void)
{
int msqid;
t_data data;

if ( -1 == ( msqid = msgget( (key_t)1234, IPC_CREAT ¦ 0666)))
{
perror( "msgget() 실패");
exit( 1);
}

while( 1 )
{
// 메시지 큐 중에 data_type 이 2 인 자료만 수신
if ( -1 == msgrcv( msqid, &data, sizeof( t_data) - sizeof( long), 2, 0))
{
perror( "msgrcv() 실패");
exit( 1);
}
printf( "%d - %s\n", data.data_num, data.data_buff);
}
}


]$ gcc main_sender.c -o sender
]$ gcc main_receiver.c -o receiver
]$ ./sender &
[1] 3660
]$ ./receiver
2 - type=2, ndx=2, http://forum.falinux.com
5 - type=2, ndx=5, http://forum.falinux.com
8 - type=2, ndx=8, http://forum.falinux.com
11 - type=2, ndx=11, http://forum.falinux.com

// 여기서 멈춤이 일어 납니다. 왜냐하면 receiver 가 data_type 가 2 인 것만
// 가져 갔기 때문에 메시지 큐가 포화되었기 때문입니다.
// 그래서 Ctrl-C 키로 receiver 를 종료한 후에
// main_reciever.c 의 msgrc() 의 인수 중 data_type 를 0 으로 변경한 후
// 다시 실행하면 큐에 쌓인 자료를 다시 읽어 들여서 큐를 비우게 되고
// sender에서 다시 전송을 계속하게 되고 receiver도 수신을 계속하게 됩니다.

]$ vi main_receiver.c // 소스를 수정합니다.
]$ gcc main_receiver.c -o receiver
]$ ./reciever
1 - type=1, ndx=1, http://forum.falinux.com3 - type=3, ndx=3, http://forum.falinux.com4 - type=1, ndx=4, http://forum.falinux.com6 - type=3, ndx=6, http://forum.falinux.com7 - type=1, ndx=7, http://forum.falinux.com9 - type=3, ndx=9, http://forum.falinux.com10 - type=1, ndx=10, http://forum.falinux.com12 - type=3, ndx=12, http://forum.falinux.com13 - type=1, ndx=13, http://forum.falinux.com14 - type=2, ndx=14, http://forum.falinux.com15 - type=3, ndx=15, http://forum.falinux.com16 - type=1, ndx=16, http://forum.falinux.com17 - type=2, ndx=17, http://forum.falinux.com18 - type=3, ndx=18, http://forum.falinux.com19 - type=1, ndx=19, http://forum.falinux.com

:

2009년 12월 11일 금요일

Real-Time Signal과 이벤트기반 네트워킹

Real-Time Signal과 이벤트기반 네트워킹

Contents
1 소개
2 개요
3 소개
4 이벤트 전달 방식
4.1 다중 연결의 처리
5 리눅스 커널 매커니즘
5.1 select() 시스템 콜
5.2 poll() 시스템 콜
5.3 POSIX. 4 Real Time Signals
6 이벤트 통지에서의 효율성
7 RTS의 단점/해결방법
7.1 Linux에서의 Signal Queue 크기
7.2 Siganl queue Overflow 문제
7.3 Signal-per-fd의 사용
8 2.4.x에서의 signal-per-fd 커널 패치
8.1 kernel 다운로드 및 패치 하기
8.2 2.6에서의 signal-per-fd
8.3 간단한 셈플 프로그램
9 프로젝트 진행
10 참고 문헌


■이 문서는 일부 2.6의 내용을 포함하고 있지만 전체적으로 커널 2.4를 기준으로 작성되었다. 최신 커널의 변경사항을 조사해서 수정할 필요가 있다.


1 소개 몇번에 걸쳐서 RTS를 다루었는데 너무 피상적인 내용만 다룬것 같다. 아무래도 제대로 사용하기 위해서는 실제로 서비스 가능한 간단한 인터넷 서비스 프로그램이라도 개발해야 할것 같다.


그러기 위해서는 몇가지 해결해야될 문제들이 있으며, 다른 이벤트 전달 방법인 select(), /dev/poll등과 비교해서 어떤 잇점을 가지고 있는지도 확실히 짚고 넘어가야 한다.


이와 관련된 문서를 찾던중 HP 연구소의 훌륭한 문서를 찾게 되었고 이 문서를 이용해서 연구를 하고 결과를 이용한 응용 애플리케이션을 작성하기로 결정했다.


2 개요 인테네트 서비스들의 문제점은 무엇일까 ?. 아마도 많은 양의 데이터를 처리때문에 골치아플 거라고 생각될 수 있게지만, 실제 겪는 진정한 골치거리는 다수의 연결을 처리해야 한다는 점이다. 보통 이러한 연결에 있어서 통신에 사용되는 데이터 자체의 크기는 그리 많지 않은 반면 요청과 응답이 매우 빈번하게 이루어 지는데, 서버측에서 보자면 이러한 요청을 빠른시간에 받을 수 있어야 하며, 요청을 누가했는지 최대한 효율적으로 판단해서 요청에 대한 응답을 보내줄 수 있어야 한다.


이 글에서 우리는 인터네트 서버에서 다수의 요청에 의한 네트워크 I/O 이벤트를 효과적으로 처리하기 위한 몇가지 방법에 대해서 알아볼 것이다. 아마도 이 사이트에서 몇번 다루어본적이 있는 (비교적 최근의 이벤트 처리기술인)RTS를 위주로 설명할 것이며, RTS가 정말로 효율적으로 이벤트를 처리할 수 있는지를 확인하기 위해서 고전적인 이벤트 처리 방식인 select()와 /dev/poll과의 성능 테스트도 함께 하게 될 것이다.


또한 RTS와 같은 레벨에서 /dev/epoll도 다루고 서로 비교할 것이다. /dev/epoll은 최근에 나온 이벤트 처리기술로 매우 좋은 성능을 보여준다고 알려져 있다. /dev/epoll에 대한 내용은 gururang님의 epoll위키를 많이 참고하게 될것이다.


RTS를 다룸에 있어서는 중복된 내용은 가급적 피하고, 몇번 언급되었지만 깊이 다루지 않은 signal-per-fd에 대한 내용을 꽤 심도 있게 다루고 실제 적용후 예제코드를 만들어 보도록 할것이다. signal-per-fd를 사용할경우 시그널을 제어하기 위한 여러가지 복잡한 내용들에 대해 신경쓰지 않게 되므로 좀더 코드에 집중할 수 있으며, 좀더 안전한 프로그램을 만들 수 있게 된다.


3 소개 이 문서의 제목이 네트워크 I/O이벤트 처리에 관한 연구이다. 왜 이러한 주제를 다루게 되었는지 인터네트와 인터네트에 관련된 기술동향들에 대해서 알아보도록 하겠다.


웹과 e-commerce가 빠른 속도로 발전하고 있으며, 이로인해 인터네트의 트래픽 역시 가파르게 증가하고 있다. 웹서비스와 관련된 네트워크 애플리케이션과 프록시(proxy)들은 전세계에서 들어오는 클라이언트의 요청을 처리할 수 있어야만 한다. 거기에다 가 서버들은 거의 동시에 발생하는 수많은 연결을 가능한 빠른시간에 처리할 수 있어야 한다. 만약 연결처리가 늦어지게 된다면 클라이언트는 서비스의 이용을 위해서 많은 시간을 기다려야 할 것이고 고객의 인내심을 벗어나게 될경우 분명히 고객을 잃어 버리게 될것이다. 특히 다른 것들(단지 데이터를 통신하는것)에 비해 연결은 매우 많은 시간을 소비한다. 고로 가능하면 빠르게 클라이언트의 연결을 감지하고 이를 처리해 낼 수 있어야 한다.


가장 인기 있는 인터네트 서비스인 웹서비스를 예로 들어보자. 웹서비스의 경우 동시에 여러개의 네트워크 I/O를 처리하기 위해서 운영체제에서 제공하는 이벤트 전달(devent-dispatch) 방식을 사용한다. 아직까지는 많은 서버들이 고전적인 이벤트 전달 방식들을 사용하는데 이들 이벤트 전달방식은 오래된 만큼 그리 효율적이지 못하다는 단점을 가진다. 아파치 웹서버의 경우 일반적인 서비스를 운영하는데에는 문제가 없지만 조금더 규모가 커지면 연결증가에 따른 많은 문제가 발생한다. 연결이 느려지거나 아예 연결시도 자체가 실패하는 경우가 대표적인 경우인데, 이러한 문제의 해결을 위해서는 웹서버 튜닝과 하드웨어 증설등에 많은 시간고 비용을 투자하게 된다.


이러한 문제를 해결하기 위해서 TUX와 같이 아예 커널 공간으로 서비스 기능을 올려서 성능을 극적으로 향상시켜 버리는 제품들도 있다. 그러나 여기에서는 이러한 것들은 다루지 않을 것이다. 이벤트 전달방식을 사용해서 이러한 문제를 해결하는데 집중하도록 하겠다.


앞으로의 장에서 우리는 select()시스템콜과 /dev/poll 인터페이스 그리고 POSIx.4 Real Time Signal(이하 RTS)의 이벤트 전달 기법들에 대해서 다룰 것이다.



4 이벤트 전달 방식 이번장에서 우리는 다중연결을 처리하는 서버들의 두가지 유형에 대해서 알아볼 것이다. 그리고 리눅스 커널에서 지원하는 여러가지 이벤트 전달방식들을 알아볼 것이다. 이러한 내용들은 이미 이사이트에서 여러번에 걸쳐서 다루어 졌으므로 주로 각각의 방식의 효율성과 단점, 어떤 것을 선택해야 하는지 등에 촛점을 맞출 것이다.


4.1 다중 연결의 처리 네트워크상에서 다중 연결을 처리하는 데는 크게 2가지 방법이 존재한다.

■쓰레드 기반 : 다중연결을 처리하기 위한 방법중 하나로 메인 쓰레드에서 accept를 이용해서 연결을 기다리고 있다가 새로운 연결소켓이 만들어지면 연결소켓만을 처리하는 쓰레드를 생성해서 쓰레드당 하나의 클라이언트를 처리하도록 하는 방식이다. 여기에는 쓰레드의 생성시간에 따라서 두가지 다른 방식이 존재한다.

■요청이 있을 때마다(on-demand)) : accept에 의해서 새로운 연결이 들어올 때마다 연결을 처리할 쓰레드를 생성하는 방식이다. 매우 직관적이고 코딩이 편하긴 하지만 쓰레드를 생성할 때 많은 비용을 지불한다는 단점을 가진다. 연결이 빈번하게 이루어지는 서버일 경우 문제가 될 수 있다.

■쓰레드 풀 방식 : on-demand방식의 단점을 극복하기 위해서 사용되는 방법으로 미리 연결을 처리할 쓰레드를 일정 갯수 생성시켜 놓는 방식이다. 메인 쓰레드는 새로운 연결이 만들어 졌을 때 연결 처리를 위한 새로운 쓰레드를 생성시키는 대신 이미 만들어진 쓰레드에 연결을 위임하는 방식을 사용한다. 이 방법을 이용하면 쓰레드 생성시 발생하는 오버헤드를 상당히 해소할 수 있다. on-demand방식에 비해서 구현이 좀더 복잡하고 연결이 적은 서버의 경우 오히려 자원을 낭비할 수 있다는 단점을 가진다.

■이벤트 기반 : 이벤트 기반 어플리케이션은 하나의 쓰레드에서 비봉쇄(non-blocking) I/O방식으로 여러개의 연결을 처리한다. 운영체제는 하나 혹은 여러개의 연결로 부터 이벤트가 발생하면 이를 어플리케이션으로 통보하고 어플리케이션은 통보된 정보를 바탕으로 이벤트 발생한 파일(소켓)을 이용해서 통신을 한다. 어플리케이션에 이벤트를 통보하기 위해서 운영체제는 만들어진 연결에 대한 파일지정자에 대한 리스트를 유지하고 있어야 한다. 운영체제는 이 파일지정자 리스트의 각 지정자에 이벤트가 발생하는지를 확인을 해서 이벤트가 발생하면 애플리케이션으로 이벤트를 전달한다.


일반적으로 연결당 쓰레드(thread-per-connection)를 생성하는 서버는 많은 쓰레드가 생성될 경우 문백교환(context switching)을 위한 상당한 오버헤드가 발생할 수 있다는 단점을 가진다. 또한 각각의 쓰레드는 자신만의 스택공간과 연결처리를 위한 별도의 루틴을 가져야 하기 때문에 메모리 공간역시 많이 차지한다는 문제점도 가지게 된다. 만약 운영체제가 커널레벨 쓰레드를 지원하지 않는다면 이러한 단점은 더욱 크게 부각될 것이다. 리눅스가 이러한 경우에 해당되는데 clone()를 통한 프로세스 방식의 쓰레드를 사용하게 되므로 다른 운영체제 보다 더욱 과도한 오버헤드와 메모리 소비를 가지게 된다. 어쨋든 간에 쓰레드(혹은 프로세스)기반의 서버는 많은 연결을 처리하는데 있어서 근본적인 문제를 가지게 된다.


위의 이유로 많은 운영체제들이 이벤트 기반처리 방식을 제공하고 있다. 물론 이벤트 기반 처리를 도입하려는 서버는 대부분 매우 바쁘게 작동할 것이라고 예상하고 작성되므로 이벤트 처리방식과 함께 다중 쓰레드 기법까지 함께 사용하게 된다. 이러한 서버들에서 어떠한 이벤트 처리방식을 사용할 것인가 하는 것은 매우 중요한 문제다. 여러 종류의 이벤트 처리방식이 존재하는데 각각 용도와 성능에 있어서 차이가 있기 때문이다. 다음장에서는 여기에 대해서 집중적으로 알아보도록 하겠다.


5 리눅스 커널 매커니즘 위에서 이벤트 기반 서버는 네트워크상의 입출력(I/O)의 처리를 위해서 이벤트 전달기법을 사용하고 있다. 이번 장에서는 리눅스 커널에서 애플리케이션에 이번트를 통지하기 위해서 사용되는 몇가지 방법들에 대해서 알아보도록 하겠다. 다음은 리눅스 커널에서 지원하는 이벤트 전달 방법들이다.


5.1 select() 시스템 콜 select()는 단일 쓰레드나 프로세스에서 열려있는 여러개의 연결을 다중화 시켜서 처리할수 있도록 허용한다. select()는 fdset을 가진다. 이 fdset은 1024크기의 bit테이블로써 여기에 관심있어하는 파일의 목록을 등록시킨다. 만약 등록시킨 파일에서 입출력이 발생하면 select()는 리턴하며 이때 fdset의 bit테이블을 설정한다. 만약 4번 파일지정자에 읽기 이벤트가 발생했다면 fdset[3]의 값을 1로 해서 넘겨주는 방식이다.


다음은 select()의 특징적인 작동이다.

1.애플리케이션은 관심있어하는 파일지정자의 목록(fdset)을 커널에 전달한다.

2.관심있는 파일지정자에 대한 정보는 fdset에 드문드문 설정될 것이다. 이 정보는 유저레벨에서 커널레벨로 복사된다.

3.커널은 fdset을 모두 뒤지면서 관심있는 파일지정자를 찾아내고 이 파일 지정자에 이벤트(읽기/쓰기 데이터가 있는지)가 발생했는지를 검사한다. 눈치챘겠지만 이것은 꽤나 비용이 드는 작업이다.

4.이 fdset은 커널 모드에서 다시 유저모드로 복사되고 select()는 리턴된다.


5.2 poll() 시스템 콜 poll()은 내부적으로 select()를 사용한다. 어찌보면 select()와 전혀 동일하다고 생각할 수도 있다. 그렇지만 인터페이스에 있어서 약간의 차이점을 보인다. poll()은 관심있는 파일지정자를 유지하기 위해서 fdset을 이용하는 대신에 pollfd 구조체를 사용한다. 만약 pollfd구조체에서 유지하고 있는 파일에 데이터가 준비되어 있다면 이 구조체를 리턴하게 된다.


poll()이 내부적으로 select()를 사용하고 있다는 것 때문에 언뜻 생각하기에 언제나 select()가 더 효율적일 거라고 생각하지만 그렇지않다. select()는 poll()에 비해서 매우 넓은 fdset범위에서 파일지정자들을 검색해야 하기 때문이다.


5.3 POSIX. 4 Real Time Signals RTS는 Unix에서 이벤트 통지를 위해 사용하는 시그널을 확장시킨 형태로 시그널을 객체로 다루며 동시에 시그널의 대기열(queue)를 유지한다. 기존의 시그널은 시그널을 비트를 세팅하는 것으로 신고하가 전달 되었는지의 유무를 관리한다. 그러므로 동일한 시그널이 빠른 시간에(핸들러가 종료하기 전에)여러번 발생하게 된다면 하나를 제외하고 모든 시그널을 잃어 버리게 된다.


더불어 시그널에 대해서 비트 설정만이 아닌 sigino를 전달하는데, 이를 통해서 여러가지 정보들까지 함께 전달 가능하다.


다음은 RTS를 이용해서 파일로 부터 이벤트를 통지 받기 위한 일반적인 코드이다. RTS응용에 대해서는 이미 여러번 다룬적이 있으므로 설명은 하지 않도록 하겠다.

// 새로운 연결이 들어왔다면
int sd = accept(...);

// 새로운 연결 소켓에 대해서 RTS설정을 한다.
fcntl (sd, F_SETOWN, getpid());
fcntl (sd, F_SETSIG, SIGRTMIN);

fcntl (sd, F_SETTL, O_NONBLOCK|O_ASYNC);


6 이벤트 통지에서의 효율성 이번장에서는 각각의 이벤트 통지방법이 어느정도의 효율성을 가지고 있는지 알아보도록 하겠다. 참고로 이 테스트의 결과는 필자의 테스트 결과가 아닌 HP 연구소에의 Scalability of Linux Event-Dispatch Mechanisms문서의 내용을 발췌한 것임을 박힌다.


이 테스트를 위해서 (HPL)은 uservers라는 조그마한 웹서버를 만들어서 테스트 했다. 물론 테스트를 위해서 각 이벤트 통지방식을 적용한 여러개의 웹서버가 만들어 졌다. 테스트는 2개의 400MHz 팬티엄III CPU를 장착한 시스템에서 이루어졌다. 커널은 2.4.0-test7버젼이다. 현재 2.4.23안정버젼까지 나오고 2.6.0-13테스트 버젼까지 나온 상태에서 현실에 약간 동떨어진 구식의 커널을 사용하긴 했지만 각 이벤트 통지 방식간 성능 차이를 비교하는데는 별 문제가 없을 것이다.
- 시간이 된다면 직접 최신의 환경을 만들어서 테스트하도록 하겠다 -


테스트 서버가 준비되었으니 테스트 클라이언트도 준비되어야 할것이다. 역시 전용의 테스트용 클라이언트가 준비되었다. 테스트용 클라이언트는 HP-UX10.20을 탑제한 B180 RA-RISC기계에서 작동을 한다. 서버와 클라이언트는 100Mbps 패스트 이더넷 스위치로 연결된다. 테스트는 많은 수의 연결을 만들었을 때 각각의 서버가 얼마나 빠르게 반응하는지 어느정도의 CPU자원을 사용하는지를 확인하는 방식으로 이루어진다.


다음은 테스트 결과다.


■동시 연결요청에 대한 응답율


■동시 연결요청이 이루어졌을 때의 CPU사용율


■동시 연결요청에 대한 응답반응 시간


■256idle 연결에 대한 성능비교


■6000 idle 연결에 대한 성능비교


■로드증가에 대한 응답시간



성능의 차이를 한번에 알아 볼수 있을 것이다.


7 RTS의 단점/해결방법 RTS가 매우 효율적이긴 하지만 몇 가지 단점들을 가지고 있다. 이번 장에서는 이러한 RTS의 단점과 이에 대한 해결방법에 대해서 알아보도록 하겠다.


7.1 Linux에서의 Signal Queue 크기 시그널 큐의 크기는 프로세스당 제한되어 있으며, 크기는 /proc/sys/kernel/rtsig-max에 정의되어 있다. 아마 1024로 설정되어 있을 것이다. 물론 필요에 따라서 간단하게 변경 가능하다.

# echo 2048 > rtsig-max
서버의 용도에 따라서 적당한 값을 이용하도록 하자.


7.2 Siganl queue Overflow 문제 이러한 단점은 시그널 대기열의 크기가 제한되어 있기 때문에 발생하는 문제들이다. 소켓에서 발생한 이벤트는 시그널의 대기열에 쌓인다. 쌓인 이벤트들은 sigwaitinfo()를 통해서 가져옮으로써 대기열에서 지워지게 된다. 그런데 특정 시간대에 서버가 매우 바뻐져서 시그널 대기열을 비우는 속도를 훨씬 초과해서 이벤트가 쌓이고 결국 시그널 대기열이 모두 차버리는 문제가 발생할 수도 있을 것이다.


시그널 큐 오버플로는 데드락(deadlock -교착상태)상태를 만들 수 있다. 또한 대기열이 꽉차게 될경우 당연히 이후에 발생하는 어떠한 시그널도 대기열에 쌓이지 못하고 버려지게 된다.


이러한 문제를 피하기 위해서 리눅스 커널은 시그널 큐 오버플로어가 발생하면 애플리케이션으로 SIGIO를 발생시킨다. 만약 RTS를 사용중 SIGIO를 통지 받았다면 애플리케이션에서 시그널 큐 오버플로어 문제를 해결해야 한다. 불행하게도 RTS에서의 시그널큐 오버플로어의 처리는 애플리케이션을 꽤나 복잡하게 만든다.



7.3 Signal-per-fd의 사용 위에서 RTS의 가장 큰 단점인 시그널 큐 오버플로어에 대해서 알아보았다. 이것은 애플리케이션의 작성을 매우 복잡하게 만든다. 그렇다면 가장 바람직한 방법은 시그널 큐 오버플로어 상황이 아예 발생하지 않도록 하는 것이다.


시그널 큐 오버플로어가 발생하게 되는 이유는 각각의 연결당 여러개의 이벤트를 받아들일 수 있다는 데에서 발생한다. 지금 4,5,6,7,...,100 의 연결이 만들어져 있다고 가정을 해보자. 이때 각각의 소켓은 제한 없이 이벤트를 받아들이게 되고 그러다 보니 이벤트의 총합이 시그널 대기열의 크기를 벗어나는 문제가 발생한다. 그렇다면 각각의 연결에 대해서 단지 하나의 시그널만 유지하도록 만든다면 시그널 큐 오버플로어 문제를 간단하게 회피할 수 있을 것이다. 그렇다면 1000개의 여결이 있다고 하더라도 최대 1000개의 이벤트만이 시그널 큐에 쌓일 수 있기 때문에 절대 시그널 큐 오버플로어가 발생할 수 없을 것이다. - 참고로 시그널 큐의 크기는 열수 있는 파일의 크기와 같다. -


이렇게 파일지정자당 하나의 시그널만 사용할 수 있게 하면 시그널 큐 오버플로어 문제를 해결가능하긴 하는데, 이렇게 될경우 성능에 있어서 희생을 가져오지 않는가 하는 의문이 발생할 수 있을 것이다. 하나의 소켓이 동시에 여러개의 이벤트를 처리할 수 없다는 것을 의미하기 때문이다. 그러나 이 문제는 그리 걱정할 상황이 되지는 않는다. 보통 하나의 소켓은 하나의 클라이언트와의 연결인데, 하나의 클라이언트에서 동시에(매우 짧은시간에) 여러개의 이벤트가 발생하는 경우는 필요하지 않기 때문이다. 일반적으로 클라이언트와 서버간에 일대일 연결이 맺어졌다면 클라이언트에서 요청을 보내고 서버가 응답을 하면 다시 클라이언트가 요청을 하는 방식이기 때문이다. 매우 바쁜 웹서버라고 할지라도 단일 클라이언트와 서버의 관점(소켓관점)에서 본다면 하나의 소켓에 대해서는 한번에 단지 하나의 이벤트만 발생한다.


결론적으로 Signal-per-fd를 사용하게 될경우 서버성능에 큰 희생 없이 시그널 큐 오버플로어를 피해나갈 수 있다.


다음은 signal-per-fd를 사용할 때 얻을 수 있는 장점들이다.

1.시그널 큐 오버플로어를 회피할 수 있으므로 프로그램이 구조적으로 단순해 진다.

2.또한 시그널 큐 자원을 안전하게 사용할 수 있도록 보장한다.

3.하나의 사건에 대해서 여러개의 이벤트가 발생할 수도 있는데, signal-per-fd를 이용하므로써 애플리케이션에게 잘정의된 하나의 이벤트만 받을 수 있도록 한다.


8 2.4.x에서의 signal-per-fd 커널 패치 이제 실제 2.4.x커널에 signal-per-fd 패치를 하고, 이를 통해서 간단한 응용 프로그램을 만들어서 테스트 해 보도록 하겠다.


8.1 kernel 다운로드 및 패치 하기 signal-per-fd가 비교적 최근에 제공된 기술인 관계로 모든 리눅스 커널에 대한 패치가 존재하지 않는다. 설사 존재한다고 하더라도 찾기 어려운 경우가 많다. 그래서 가장 얻기 쉬운 패치를 기준으로 커널을 다운로드 받아서 패치후 컴파일 하기로 했다.


google에서 signal-per-fd patch로 찾은 결과 커널 2.4.13에 대한 signal-per-fd커널 패치를 쉽게 찾을 수 있었다. 커널 2.4.13는 http://www.kernel.org에서 받으면된다. signal-per-signal패치는 one-sig-perfd-2.4.13.pat를 다운로드 받으면 된다.


다운로드 받은 커널소스는 /usr/src/linux-2.4.13에 푼다. 그후 패치파일을 linux-2.4.13디렉토리로 이동한후 다음과 같이 패치를 하도록한다.

# patch -p 1 < one-sig-perfd-241.pat
...
위의 패치파일을 보면 다음과 같은 내용을 발견할 수 있다.

+ /* For one signal per fd discipline, indicate signal
+ * delivery so a new one can be queued
+ */
+ if (sig >= SIGRTMIN) {
+ struct file *filep = fcheck( q->info.si_fd );
+
+ if( filep && (filep->f_auxflags & O_ONESIGFD) &
+ ( q == (siginfo_t *) filep->f_infoptr ) )
+ filep->f_infoptr = NULL;
+ }
+
/* Copy the sigqueue information and free the queue entry */
copy_siginfo(info, &q->info);
kmem_cache_free(sigqueue_cachep,q);
위의 코드를 보면 f_auxflags의 설정 여부에 따라서 signal-pre-fd의 적용여부가 결정됨을 확인 할 수 있다. 실제 코드 상에서는 아래처럼 fcntl을 이용해서 해서 signal-per-fd를 적용시킨다.

fcntl(sockfd, F_SETAUXFL, O_ONESIGFD);


패치를 끝냈다면 이제 커널컴파일을 하도록 한다. 커널 컴파일 방법은 여기에서 언급하지 않도록 하겠다.


8.2 2.6에서의 signal-per-fd 2.6.x커널에서 signal-pre-fd의 지원을 확인 하기 위해서 kernel/signal.c파일을 확인해 보았다. 정확한 커널버젼은 2.6.0-test11이였으며, 다음과 같은 내용을 확인 할 수 있었다.


■아래 코드에 대해선 해석이 필요함..

/* Real-time signals must be queued if sent by sigqueue, or
some other real-time mechanism. It is implementation
defined whether kill() does so. We attempt to do so, on
the principle of least surprise, but since kill is not
allowed to fail with EAGAIN when low on memory we just
make sure at least one signal gets delivered and don't
pass on the info struct. */

if (atomic_read(&nr_queued_signals) < max_queued_signals)
q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC);
if (q) {
atomic_inc(&nr_queued_signals);
q->flags = 0;
list_add_tail(&q->list, &signals->list);
switch ((unsigned long) info) {
case 0:
q->info.si_signo = sig;
q->info.si_errno = 0;
q->info.si_code = SI_USER;
q->info.si_pid = current->pid;
q->info.si_uid = current->uid;
break;
case 1:
q->info.si_signo = sig;
q->info.si_errno = 0;
q->info.si_code = SI_KERNEL;
q->info.si_pid = 0;
q->info.si_uid = 0;
break;
default:
copy_siginfo(&q->info, info);
break;
}
} else {
if (sig >= SIGRTMIN && info && (unsigned long)info != 1
&& info->si_code != SI_USER)
/*
* Queue overflow, abort. We may abort if the signal was rt
* and sent by user using something other than kill().
*/
return -EAGAIN;
if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))
/*
* Set up a return to indicate that we dropped
* the signal.
*/
ret = info->si_sys_private;
}


8.3 간단한 셈플 프로그램 1.signal-per-fd를 적용한 간단한 프로그램을 만들고 이에 대한 테스트를 실시한다.

9 프로젝트 진행 1.signal-per-fd 커널 패치가 적용된 리눅스에서의 RTS웹서버 제작 프로젝트를 수행한다. 실질적인 프로젝트로 발전시키도록 한다.



10 참고 문헌

1.Real Time Signal - 1

2.Real Time Signal -2

3.RTS와 쓰레드풀과의 조합

4.Scalability of Linux Event-Dispatch Mechanisms

5.Scalability of Linux Event-Dispatch Mechanisms (ps)

6.구루랑님의 epoll 연구 위키

7.http://www.kegel.com/c10k.html

리눅스 시스템 프로그래밍 7장 쓰레드

리눅스 시스템 프로그래밍 쓰레드

1 Thread에 대해서
프로그램을 병렬로 실행시키는 방법으로 fork()에 대해서 알아보았다. fork()는 매우 이해하기 쉬운 프로그래밍 방법이긴 하지만 자원효율성에서 몇가지 문제점을 가지고 있다. 프로세스는 기본적으로 code, data, stack, file I/O, 그리고 signal table의 5가지 요소로 구성이 된다. fork() 를 이용해서 새로운 프로세스를 생성하게 되면, 이러한 5가지 구성요소가 모두 복사가 된다. 그러하다 보니 프로세스를 생성하는데 많은 비용이 소비될 것이다. 대게의 경우에는 프로세스를 새로 생성시킬때 발생하는 성능저하가 문제가 되지는 않겠지만 웹서비스처럼 대량의 접근이 발생하는 영역에서는 문제가 될 수 있다.

fork의 이러한 방식은 상당히 효율이 떨어지는 측면이 있다. 어떤 프로그램을 병렬로 실행시킨다고 했을 때, 실제 우리가 병렬로 실행되기를 원하는 영역은 코드의 일부분이지 프로그램 전체는 아니기 때문이다.

// ... pid = fork(); if (pid > 0) { // 실제는 이 부분의 코드만 병렬로 실행되면 된다. // fork()는 다른 모든영역의 코드가 복사되어 버린다. }
게다가 전혀다른 프로세스를 생성시킴으로써, 프로세스간 통신이라는 상당히 복잡한 문제까지를 해결해야 한다. 병렬로 작동하는 프로그램은 특성상 데이터를 공유하거나 서로 통신을 해야 하는 경우가 많다. 그런데 프로세스는 서로 독립된 객체이므로 일반적인 방법으로는 데이터를 공유할 수가 없다. 이러한 프로세스간 데이터 통신을 위해서 리눅스는 IPC라는 설비를 제공하는데, IPC라는게 사용하기가 여간 까다롭지가 않다. IPC에 대해서는 별도의 장을 할애해서 다룰 계획이다.

Thread를 이용하면 fork()를 이용한 프로세스 기반의 병렬처리의 문제점의 많은 부분을 해결할 수 있다. Thread는 새로운 프로세스를 생성시키지 않고, 특정 문맥(코드)만을 병렬로 실행할 수 있도록 허용한다. 새로운 프로세스를 생성시키지 않기 때문에 그만큼 자원을 아낄 수 있으며, 더 효율적으로 빠르게 움직일 수 있다. 또한 같은 프로세스이기 때문에, 데이터를 공유하기가 쉽다는 장점도 가진다.

1.1 Thread vs Process
Thread는 프로세스와 다음과 같은 차이점을 가진다.

•프로세스는 독립적이다. 쓰레드는 프로세스의 서브셋이다.

•프로세스는 각각 독립적인 자원을 가진다. 쓰레드는 stat, memory 기타 다른 자원들을 공유한다.

•프로세스는 자신만의 주소영역을 가진다. 쓰레드는 주소영역을 공유한다.

•프로세스는 IPC를 이용해서만 통신이 가능하다.

•일반적으로 쓰레드의 문맥교환(context switching)는 프로세스의 문맥교환보다 빠르다.


1.2 Multi Thread 프로그램의 단점
모든 도구가 그러하듯이 Multi Thread 프로그램이라고 해서 장점만 가진 것은 아니다. Multi Thread 프로그램은 Multi Process 프로그래밍 방식에 비해서 다음과 같은 단점을 가진다.

•하나의 쓰레드에서 발생된 문제가 전체 프로세스에 영향을 미친다.

멀티 프로세스의 경우에는 프로세스하나가 문제가 생기더라도 단일 프로세스로 문제가 제한된다. 그러나 멀티쓰레드 프로그램의 경우 하나의 쓰레드에 생긴 문제가 다른 쓰레드에까지 영향을 줄 수 있다. 예를 들어 쓰레드 하나가 다른 프로세스의 메모리 영역을 침범할 경우 프로세스 자체가 죽어버림으로써, 프로세스에 생성된 다른 모든 쓰레드도 프로세스와 함께 죽어버리게 된다. - 이 문제는 해결 가능하지만 여기에서는 다루지 않도록 하겠다. 시그널을 잘 활용하면 된다. 관심있으면 한번 고민해 보기 바란다. -

•디버깅이 어렵다. 문맥이 서로 교환되므로 추적하기가 까다롭다.


이러한 단점이 있음에도 불구하고 멀티쓰레딩 프로그래밍 기법을 선호하고 있다.

1.3 PThread
Thread는 운영체제에서 제공하는 병렬처리 메커니즘으로, 실제 이 메커니즘을 이용하기 위해서는 Thread의 구현체가 필요하다.

리눅스에서는 pthread라는 thread 구현 라이브러리가 사용되고 있다. pthread는 POSIX thread 의 줄임말로 POSIX 표준을 따르고 있다. pthread는 리눅스 뿐만 아니라 다른 거의 대부분의 유닉스에서도 사용할 수 있다. 이외에도 BSD 계열에서 사용하는 'Light Weight Kernel Threads , Apple 에서 사용하는 Multiprocessing Services등의 구현체가 있다. 이 문서는 pthread구현만을 설명하도록 할 것이다.

pthread는 리눅스 운영체제에서 제공하는 thread 를 제어하기 위한 함수들을 모아 놓은 C 라이브러리로, 다음과 같은 기능의 함수군을 제공한다.

•쓰레드 생성과 종료 관련 함수들

•쓰레드 동기화 관련 함수들

쓰레드는 많은 데이터를 공유한다. 그러므로 데이터에 대한 동기화 문제를 해결해야할 필요가 있다.

•쓰레드 시그널 제어 함수들

signal은 프로세스단위로 작동한다. 그러나 쓰레드 프로그램의 경우, 각 쓰레드 마다 다른 시그널 정책이 필요하므로, 쓰레드 전용의 시그널 제어 함수가 필요하다.


1.4 Multi Thread 프로그램
병렬로 작동하지 않는 하나의 문맥흐름만을 가지는 프로그램을 단일 쓰레드 프로그램이라고 한다. 반대로 아래와 같이 문맥이 나뉘어서, 동시에 두개 이상의 쓰레드가 실행되면, 이를 멀티 쓰레드 프로그램'''이라고 한다.



1.5 Process, Kernel Thread, User Thread
프로세스는 가장 무거운 커널의 스케쥴링 단위이다. 프로세스는 운영체제에게 할당받은 자원들 - 파일 핸들러,소켓,장치 핸들러 - 을 할당받게 된다. 프로세스는 독립된 단위로써 파일이나 주소영역 등을 공유하지 않는다.

kernel thread는 가장 가벼운 커널 스케쥴링 단위다. 하나의 프로세스는 적어도 하나의 커널 쓰레드를 가지게 된다. 만약에 프로세스가 하나이상의 쓰레드를 가지고 있다면, 이들 쓰레드는 같은 메모리와 파일자원등을 공유하게 된다. 만약 커널의 프로세스 스케쥴러가 선점형이라면 쓰레드의 스케쥴러도 선점형인 경우가 많다. 참고삼아서 선점형과 비선점형에 대해서 간략하게 설명하도록 하겠다.

•비선점형 : 특정 프로세스가 CPU를 독점하는 것이 가능하다.

•선점형 : 특정 프로세스가 CPU를 독점하는게 불가능하다.

특정 프로세스가 CPU를 독점하는게 불가능하게 하는 것은 프로세스가 인터럽트를 무시하기 못하게 하는 것으로 구현한다. 선점형은 어떤 프로세스가 시스템콜을 수행중이더라도 커널이 인터럽트를 보내면, 즉시 빠져 나와야 한다. 즉 운영체제가 CPU를 선점한다는 얘기가 된다. 시스템콜이 수행중이더라도 인터럽트를 걸고 다른 일을 수행하도록 할 수 있으므로 보다 빠른 반응성을 보여준다.

때때로 쓰레드가 유저영역 라이브러리로 구현되는 경우가 있는데, 이를 user Thread 라고 부른다.


1.6 쓰레드의 생성과 종료
멀티 쓰레드 프로그램이라고 하더라도, 처음 시작되었을 때는 main()에서 시작되는 단일 쓰레드 상태로 작동이 된다. 이 상태에서 pthread_create(3) 함수를 호출함으로써, 새로운 쓰레드를 생성할 수 있다. pthread_create를 이용해서 생성된 새로운 쓰레드를 worker 쓰레드라고 하자.

멀티 쓰레드 프로그램은 다음과 같은 흐름을 가진다.



생성된 worker thread는 언젠가 종료가 될 것이다. Master Thread (이하 부모 쓰레드)는 pthread_join()을 이용해서 worker thread들의 종료를 기다린다. pthread_join()는 종료된 worker thread의 자원을 정리하는 일을 한다. fork()를 이용한 멀티 프로세스 프로그램에서, 부모 프로세스가 wait()를 이용해서 자식 프로세스를 기다리는 것과 같은 이유라고 보면 된다.


1.6.1 pthread_create : 쓰레드 생성
pthread_create(3)함수를 이용하면 새로운 쓰레드를 생성할 수 있다. 이 함수는 다음과 같이 사용할 수 있다.

#include int pthread_create(pthread_t * thread, pthread_attr_t *attr, void * (*start_routine)(void *), void * arg); 1.thread : 쓰레드가 성공적으로 생성되었을 때, 넘겨주는 쓰레드 식별 번호.

2.attr : 쓰레드의 특성을 설정하기 위해서 사용한다. NULL일 경우 기본 특성

3.start_routine : 쓰레드가 수행할 함수로 함수포인터를 넘겨준다.

4.arg : 쓰레드 함수 start_routine를 실행시킬 때, 넘겨줄 인자

이 함수는 성공적으로 수행되었다면, 0을 리턴한다. 그렇지 않을 경우 1을 리턴한다.


1.6.2 pthread_join : 쓰레드 정리
쓰레드가 실행시키는 것은 함수 이다. 그러므로 return이나 exit(0)등을 이용해서 쓰레드를 종료시킬 수 있게 된다. 그러나 쓰레드 함수가 종료되었다고 해서 곧바로 쓰레드의 모든자원이 종료되지 않는다. fork()기반의 멀티프로세스 프로그램에서 종료된 자식프로세스를 정리하기 위해서 wait()로 기다리듯이, 종료된 쓰레드를 기다려서 정리를 해주어아만 한다. 그렇지 않을 경우 쓰레드의 자원이 되돌려지지 않아서 메모리 누수현상이 발생하게 된다.

pthread_create()로 생성시킨 쓰레드는 pthread_join()을 통해서 기다리면 된다. pthread_join 함수는 다음과 같이 사용할 수 있다.

#include int pthread_join(pthread_t th, void **thread_return); 1.th : pthread_create에 의해서 생성된, 식별번호 th를 가진 쓰레드를 기다리겠다는 얘기다.

2.thread_return : 식별번호 th인 쓰레드의 종료시 리턴값이다.


pthread_join이 하는 일은 명확하다. 다만 주의 할것은 pthread_join은 반드시 joinable 한 상태로 생성된 쓰레드만을 기다릴 수 있다는 점이다. pthread_create로 쓰레드를 생성시킬 때, 나중에 join되지 않을 것으로 생각하고 생성시킬 수 있는데, 이렇게 되면 이 쓰레드는 종료하자마자 모든 자원을 해제하며, pthread_join으로 기다릴 수가 없다. 부모쓰레드와 떨어져서 완전히 독립적으로 작용한다고 하여, 이를 detach 한다고 한다. 쓰레드를 detach하는 방법은 아래에서 다룰 것이다.


1.6.3 쓰레드 생성 예제
pthread_create와 pthread_join을 알고 있다면, 이제 thread를 생성시킬 수 있다.

#include #include #include #include // 쓰레드 함수 void *t_function(void *data) { int id; int i = 0; id = *((int *)data); while(1) { printf("%d : %d\n", id, i); i++; sleep(1); } } int main() { pthread_t p_thread[2]; int thr_id; int status; int a = 1; int b = 2; // 쓰레드 생성 아규먼트로 1 을 넘긴다. thr_id = pthread_create(&p_thread[0], NULL, t_function, (void *)&a); if (thr_id < 0) { perror("thread create error : "); exit(0); } // 쓰레드 생성 아규먼트로 2 를 넘긴다. thr_id = pthread_create(&p_thread[1], NULL, t_function, (void *)&b); if (thr_id < 0) { perror("thread create error : "); exit(0); } // 쓰레드 종료를 기다린다. pthread_join(p_thread[0], (void **)&status); pthread_join(p_thread[1], (void **)&status); return 0; } 아주 전형적인 프로그램이긴 하지만 pthread_join부분에 문제가 있다. pthread_join은 쓰레드가 종료될 때까지 블럭되기 때문이다. 이래서는 쓰레드를 두개이상 생성시키지 못할 것이다. 그렇다고 pthread_join을 이용하지 않는다면, 메모리 누수가 생기게 되니, 생략할 수도 없는 노릇이다.


1.6.4 자식쓰레드를 부모쓰레드로 부터 분리하기
pthread_join의 사용으로 발생할 수 있는 문제점을 해결하기 위한, 가장 좋은 방법중의 하나는 pthread_detach 를 이용해서, 자식 쓰레드를 부모쓰레드와 완전히 분리해 버리는 방법이다. 이 경우 자식 쓰레드가 종료되면, 모든 자원이 즉시 반환된다. 반면, 자식 쓰레드의 종료상태를 알 수 없다는 문제가 발생한다. 대게의 경우 자식 쓰레드의 종료상태가 중요한 문제가 되지는 않을 것이다.

만약 자식 쓰레드의 종료상태를 알아내는게 중요하다면, 종료상태를 저장할 전역변수를 두고, 여기에 종료상태를 기록하는 방식을 사용할 수 있을 것이다. 자식 쓰레드가 종료할때, 변수의 값을 바꾸고, 부모쓰레드에 시그널을 전송하는 방법이다. 이 방법은 이 문서의 뒤에서 따로 다루도록 하겠다.

#include #include #include #include // 쓰레드 함수 // 1초를 기다린후 아규먼트^2 을 리턴한다. void *t_function(void *data) { char a[100000]; int num = *((int *)data); printf("Thread Start\n"); sleep(5); printf("Thread end\n"); } int main() { pthread_t p_thread; int thr_id; int status; int a = 100; printf("Before Thread\n"); thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a); if (thr_id < 0) { perror("thread create error : "); exit(0); } // 식별번호 p_thread 를 가지는 쓰레드를 detach // 시켜준다. pthread_detach(p_thread); pause(); return 0; }

1.7 쓰레드 동기화
이제 우리는 간단한 다중쓰레드 프로그램을 만들 수 있게 되었다. 그러나 이들 쓰레드 생성 함수만 가지고는 복잡한 쓰레드 프로그램을 만들 수가 없다. 쓰레드간 동기화라고 하는 문제가 놓여있기 때문이다. 아주 간단한 프로그램이 아닌한은 반드시 동기화문제를 고민해야만한다.

동기화란 여러가지 의미로 사용될 수 있는데, 이 경우에 있어서 동기화는 서로의 시간을 맞춘다를 의미한다. 멀티쓰레드 프로그램은 하나의 시간에 여러개의 프로세스가 돌아가는 형태를 취한다. 또한 멀티쓰레드 프로그램은 자원의 상당부분을 서로 공유하는 경우가 많다. 만약 단지 자원을 읽어들이는 거라면 상관없지만 읽고/쓰는 것이라면 동기화와 관련된 문제가 발생할 수 있다.

예컨데 다음과 같은 경우다.

1.A와 B 두개의 프로세스가 있다. 이 프로세스는 int count=1 이라는 자원을 공유한다.

2.A가 count를 읽어들이고 1을 더한다.

3.B가 count를 읽어들인다. 아직 A가 count에 쓰지 않았기 때문에, B도 1을 읽어들인다.

4.A가 count에 2를 쓴다.

5.B도 count에 2를 쓴다.

6.count에는 2가 저장되었다.

우리가 원하는 값은 2가 아닌 3이다. 그러나 쓰레드가 동기화 되지 않음으로써, 원치않은 잘못된 연산을 하게 되었다. 우리는 이 문제를 해결해야 한다.


1.8 접근제어
동기화 문제는 현실세계에서도 자주 발생한다. 화장실을 생각하면 된다. 화장실은 공유자원이며, 여러명이 사용한다. 누군가 화장실을 사용하고 있다면, 다른 사람은 화장실을 사용하면 안된다. 이 문제를 우리는 접근을 제어하는 방식으로 해결한다. 문을 걸어 잠궈서 한번에 한사람만 화장실에 들어가도록 하는 방법이다. 매우 이해하기 쉬운 방식이다.

다중쓰레드 프로그램에서도 마찬가지로 접근제어를 이용해서 이 문제를 해결한다. 이를 위해서 pthread는 mutex라는 잠금 메커니즘을 제공한다.


1.8.1 mutex 잠금
동시에 여러개의 쓰레드가 하나의 자원에 접근하려고 할때 발생하는 문제를 pthread는 임계영역을 두는 것으로 해결하고 있다. 임계영역안에는 접근하고자 하는 자원이 놓여있고, 오직 하나의 쓰레드만 임계영역안으로 진입할 수 있도록 제한한다. pthread는 이를 위해서 mutex를 제공한다. mutex는 그 자체가 가지는 잠금의 특성 때문에 mutex 잠금이라고 불리워지기도 한다.



위 그림은 mutex가 작동하는 방식을 보여준다. thread 1이 자원에 접근하면 mutex 잠금을 얻게 된다. 이 잠금은 단지 하나만 존재하기 때문에 thread 2는 잠금을 얻지 못하고 임계영역 밖에서 대기하게 된다. thread 1이 자원을 모두 사용하고 임계영역을 벗어나면 thread 2는 잠금을 얻게 되고 임계영역에 진입해서 자원을 사용할 수 있게 된다.


1.8.2 mutex의 사용
mutex를 사용하기 위해서는 다음의 4가지 함수가 필요하다.

•mutex 잠금객체을 만드는 함수

•mutex 잠금을 얻는 함수

•mutex 잠금을 되돌려주는 함수

•mutex 잠금객체를 제거하는 함수


1.8.3 pthread_mutex_init
mutex를 사용하기 위해서는 먼저 pthread_mutex_init() 함수를 이용해서, mutex 잠금 객체를 만들어줘야 한다.

pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr *attr); 이 함수는 두개의 인자를 필요로 한다.

1.mutex : mutex 잠금객체

2.mutex_attr : mutex는 fast, 'recursive, error checking의 3종류가 있다. 이 값을 이용해서 mutex 타입을 결정할 수 있다. NULL 일경우 기본값이 fast가 설정된다.

◦fast : 하나의 쓰레드가 하나의 잠금만을 얻을 수 있는 일반적인 형태

◦recursive : 잠금을 얻은 쓰레드가 다시 잠금을 얻을 수 있다. 이 경우 잠금에 대한 카운드가 증가하게 된다.

mutex_attr을 위해서 다음의 상수값이 예약되어 있다.

•fast : PTHREAD_MUTEX_INITIALIZER

•recursive : PTHREAD_RECURSIVE_MUTEX_INITIALIZER

•error checking : PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP


1.8.4 pthread_mutex_lock
mutex 잠금을 얻기 위한 함수다.

int pthread_mutex_lock(pthread_mutex_t *mutex); mutex 잠금을 얻는다라는 표현보다는 mutex 잠금을 요청한다라는 표현이 더 정확할 것 같다. 만약 mutex 잠금을 선점한 쓰레드가 있다면, 선점한 쓰레드가 mutex 잠금을 되돌려주기 전까지 이 코드에서 대기하게 된다.

때때로 잠금을 얻을 수 있는지만 체크하고 대기(블럭)되지 않은 상태로 다음 코드로 넘어가야할 필요가 있을 수 있을 것이다. 이 경우에는 아래의 함수를 사용하면 된다.

int pthread_mutex_trylock(pthread_mutex_t *mutex); 1.8.5 pthread_mutex_unlock
mutex 잠금을 되돌려주는 함수다.

int pthread_mutex_unlock(pthread_mutex_t *mutex);

1.8.6 mutex 잠금 예제
count 프로그램을 예제로 할 것이다. 임계영역안에서 보호되어야할 자원은 count이고, 여러개의 쓰레드가 count에 접근해서 +1을 시도하려고 한다. 이때 제대로된 count를 위해서는 한번에 하나의 쓰레드만이 counting을 하도록 해야할 것이다. mutex를 이용해서 임계영역을 보호하도록 할 것이다.

임계영역을 보호하지 않을 경우 다음과 같은 문제가 발생할 수도 있을 것을 예상할 수 있다.

int a = 1; Thread A 에서 a를 읽어들인다. Thread B 에서 a를 읽어들인다. Thread A 에서 a = a+1를 한다. { a = a+1; 결과는 2; } Thread B 에서 a++를 한다. { a = a + 1; // 읽어들인 값이 1이기 때문에 역시 결과는 2가 된다. } 두번의 count가 발생했기 때문에 3이되어야 하겠지만 임계영역이 보호되지 않음으로써 2가 되어 버렸다.

mutex는 임계영역을 잠금으로서 이러한 문제를 해결한다. 이러한 문제를 해결하기 위해서는 임계영역에 단지 하나의 쓰레드만 접근하는걸 보장해줘야 할 것이다. mutex는 아래의 요소들을 보장함으로써 이를 보장한다.

•Atomicity - mutex 잠금은 최소단위 연적 - atomic operation - 을 보장한다. atomic operation에 대해서 간단히 설명하고 넘어간다. 자세한 내용은 Atomic Operation을 참고하기 바란다.

1.aotomic operation은 일련의 연산 즉 mutex 잠금 연산이 끝날때 까지 다른 프로세스가 그 연산의 변화를 알 수 없는 상태가 되는 연산을 의미한다. (일반적으로 연산은 이전의 연산의 결과를 관찰한 후에서야 이루어질 수 있게다)

2.전체연산중 하나라도 실패할 경우 모든 연산이 실패하며 시스템은 전체 연산이 시작하기 전의 상태로 복구된다.

•Singularity : 한 쓰레드가 뮤택스 잠금을 얻었다면, 이 쓰레드가 뮤택스 잠금을 내어놓기 전까지는 다른 쓰레드가 뮤택스 잠금을 얻을 수 없도록 한다.

•None Busy Wait : 이것은 성능과 관련된 것이다. 바쁜대기상태에 놓이지 않는다는 뜻이다. 뮤택스 잠금을 얻을 수 있는지를 확인하기 위한 연산이 필요하지 않는 다는 의미로 받아들이면 될 것이다.


이상 mutex는 위의 3가지를 지원하는 것으로 공유되는 자원을 충돌없이 그리고 효율적으로 사용할 수 있도록 보장해준다.

다음은 mutex를 사용한 count 예제프로그램이다.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960 #include #include #include int ncount; // 쓰레드간 공유되는 자원 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 쓰레드 초기화 // 쓰레드 함 수 1 void* do_loop(void *data) { int i; pthread_mutex_lock(&mutex); // 잠금을 생성한다. for (i = 0; i < 10; i++) { printf("loop1 : %d", ncount); ncount ++; sleep(1); } pthread_mutex_unlock(&mutex); // 잠금을 해제한다. } // 쓰레드 함수 2 void* do_loop2(void *data) { int i; // 잠금을 얻으려고 하지만 do_loop 에서 이미 잠금을 // 얻었음으로 잠금이 해제될때까지 기다린다. pthread_mutex_lock(&mutex); // 잠금을 생성한다. for (i = 0; i < 10; i++) { printf("loop2 : %d", ncount); ncount ++; sleep(1); } pthread_mutex_unlock(&mutex); // 잠금을 해제한다. } int main() { int thr_id; pthread_t p_thread[2]; int status; int a = 1; ncount = 0; thr_id = pthread_create(&p_thread[0], NULL, do_loop, (void *)&a); sleep(1); thr_id = pthread_create(&p_thread[1], NULL, do_loop2, (void *)&a); pthread_join(p_thread[0], (void *) &status); pthread_join(p_thread[1], (void *) &status); status = pthread_mutex_destroy(&mutex); printf("code = %d", status); printf("programing is end"); return 0; }


1.9 앞으로 할 것
쓰레드는 매우 광범위한 주제로 여기에서는 쓰레드를 사용하기 위한 가장 기본적인 내용만 다루었다. 쓰레드에 대한 좀더 자세한 내용은 별도의 장을 할애해서 다룰 생각이다.

Linux Realtime Signal

Linux Realtime Signal

Real Time Signal
윤 상배
yundream@www.joinc.co.kr


교정 과정
교정 1.1 2003년 8월 25일 23시
프로세스간 RTS전달 추가
교정 1.0 2003년 8월 23일 23시
sigqueue함수 설명 추가
교정 0.8 2003년 8월 13일 23시
최초 문서작성


--------------------------------------------------------------------------------

차례
1절. 소개
2절. poll(2)을 이용한 이벤트 통지2.1절. poll(2)의 인터페이스2.1.1절. poll(2)의 문제점3절. POSIX RTS
3.1절. RTS란
3.2절. RTS와 표준 시그널(signal)과의 비교3.3절. RTS 대기열의 크기
3.4절. RTS와 poll과의 비교
3.5절. RTS지원 확인
3.6절. RTS를 이용한 네트워크 입출력 처리
3.7절. RTS 네트워크 예제 작성
3.8절. 프로세스간 신호전달
4절. 결론

--------------------------------------------------------------------------------

1절. 소개
이 사이트의 네트워크 프로그래밍 관련 문서들을 몇개 읽어 보았다면 분명 RealTime signal(이하 RTS)에 대해서 들어 보았을 것이다. 지금까지의 네트워크 프로그래밍에서 사용되었던 기술들은 polling기반이였다. 즉 메시지가 도착하기를 계속 체크하는 방식으로 입출력을 처리하는 방식이다. 이러한 입출력방식(주로 select(2)와 poll(2) 을 응용한)으로도 대부분의 네트워크 입출력을 처리하기에는 충분하지만 최근 인터넷상에서 처리해야할 데이터의 양이 늘어남에 따라 몇몇 경우에 있어서 고전적인 방법으로 한계를 드러내게 되었다.

그래서 제안된 방법이 RTS를 이용한 시그널 기반의 입출력 처리 기법이다. RTS는 시그널의 확장판이다. 기존의 시그널이 큐잉이 되지 않으며, 전달 되었을 때 아무런 정보를 알려주지 않는 반면 RTS는 시그널 처럼 (거의) 실시간에 전달되며 입출력 데어터의 원할한 처리를 위한 필요한 정보들까지 함께 전달한다. 게다가 시그널의 대기열(큐)를 유지해서 여러개의 시그널이 짧은 시간에 도착하더라도 시그널을 잃어 버리는 문제를 해결 했다.

이 문서는 RTS에 대한 개념소개와 응용을 담고 있다. 특성상 poll(2)와 같은 함수와 자주 비교될 것이다.


--------------------------------------------------------------------------------

2절. poll(2)을 이용한 이벤트 통지2.1절. poll(2)의 인터페이스네트워크 상에서의 많은 클라이언트로 부터의 데이터의 처리를 위해서 사용되는 전통적인 방법은 select(2)나 poll(2)을 이용해서 소켓(파일)로 부터의 이벤트를 검사하는 방법이다. 다음은 poll(2)의 일반적인 인터페이스 이다.

/* Flags to indicate socket events. 0 indicates no event. */

#define POLLIN 0x0001 /* There is data to read */
#define POLLPRI 0x0002 /* There is urgent data to read */
#define POLLOUT 0x0004 /* Writing now will not block */
#define POLLERR 0x0008 /* Error condition */
#define POLLHUP 0x0010 /* Hung up */
#define POLLNVAL 0x0020 /* Invalid request: fd not open */

struct pollfd {
int fd;
short events;
short revents;
};

int poll(struct pollfd *pfds, int number, int timeout);

대략적인 작동 방식은 pfds에 등록된 파일(소켓)에 어떤 이벤트가 있는지 검사를 해서, 이벤트가 발생되었다면 이를 리턴하는 식이다. 자세한 내용은 다중연결서버 만들기 (3)를 참고하기 바란다.


--------------------------------------------------------------------------------

2.1.1절. poll(2)의 문제점poll(2)함수는 다음과 같은 몇 가지의 단점을 가지게 되고, 이러한 단점들 때문에 동시에 많은 수의 클라이언트를 다루는데 비효율이라는 문제점을 가지게 된다.


■pool(2)시스템 콜은 이벤트를 받기 위해서 커널 스페이스에서 유저 스페이스로 이벤트를 복사한다. 그리고 업데이트된 이벤트 리스트를 유저 스페이스에서 커널 스페이스로 다시 복사한다. 즉 하나의 이벤트를 전달받기 위해서 2번의 복사가 발생한다. 일반적으로 복사는 상당히 많은 자원을 소모한다.

■커널과 어플리케이션 양쪽 모두 이벤트가 발생한 소켓을 검사하기 위해서 열린 소켓모두를 검사해야 한다.

■보통 연결된 소켓중에서 단 10%에서 20%만이 활동하고 있는 소켓이다. poll(2)은 이 10%에서20%의 활성화된 소켓, 그중에서도 단지 몇개의 이벤트 발생한 소켓을 찾아내기 위해서 수십 혹은 수백개의 소켓을 뒤지는 작업을 반복해야 한다.




--------------------------------------------------------------------------------

3절. POSIX RTS
이러한 poll()과 select()의 문제를 해결하기 위해서 몇가지 새로운 방법들이 제안되었다. declare_interest()와 get_next_event()와 같이 이벤트가 발생한 소켓을 등록하고 되돌려주는 함수, 커널과 유저사이의 데이터 복사를 줄이는 방식으로 poll()을 좀더 향상시킨 /dev/poll등이 만들어져 있다. 그리고 FreeBSD운영체제의 kqueue와 같은 것들이 있다. /dev/poll은 poll()보다 성능적으로 향상되어 있지만 여전히 커널 레벨에서 모든 열린 소켓을 뒤져야 한다는 문제점을 가지고 있다. kqueue는 poll()에서 발생할 수 있는 성능 저하 문제를 해결하면서도 RTS가 가지는 사용상의 어려움까지 해결한(쉽게 사용할 수 있는)매우 매력적인 도구이다. 안타깝게도 현재 리눅스 정식커널 2.4.x에서는 kqueue를 지원하지 않고 있다. 그러나 이미 관련된 패치가 나오고 있으니 아마 2.6.x에서는 정식으로 지원할 것같다.

이 문서에서는 현재 정식으로 지원되고 있는 RTS만을 설명 할 것이다. 이하 RTS란 POSIX RTS를 칭한다.


--------------------------------------------------------------------------------

3.1절. RTS란
RTS는 비동기(asynchronous) 이벤트를 전달하기 위한 목적으로 만들어 졌으며, 주로 네트워크 애플리케이션 작성시 소켓 이벤트를 통보하기 위해서 사용한다. RTS는 네트워크 입출력에 있어서 polling에 비해 월등한 성능 향상을 보장해 준다.

시그널의 장점인 실시간성을 유지하면서 단점인 대기열부재의 문제를 해결한 향상된 시그널도구라고 이해할 수 있다.


--------------------------------------------------------------------------------

3.2절. RTS와 표준 시그널(signal)과의 비교RTS는 다음의 두가지 점에 있어서 유닉스 표준 시그널과 크게 다르다.


■유닉스 표준 시그널은 시그널 발생시 단지 시그널이 전달되었다는 사실과 전달된 시그널의 번호만을 알 수 있다. 반면 RTS는 siginfo_t 구조체에 시그널에 관련된 여러가지 정보까지 함께 전달 된다.

typedef struct siginfo {
int si_signo; /* Signal number */
int si_errno; /* Error code */
int si_code;
pid_t si_pid;
uid_t si_uid;
void *si_addr;
union sigval si_value;
union {
/* Skipping other fields */
struct {
int _band; /* Socket event flags (similar to poll) */
int _fd; /* Socket fd where event occurred */
} _sigpoll;
} _sifields;
} siginfo_t;

#define si_fd _sifields._sigpoll._fd

위의 구조체를 보면 시그널 번호는 물론이고, 어떤 소켓에서 이벤트를 발생시켰는지에 관한 기타 여러가지 정보들을 가지고 있음을 알 수 있다. RTS를 사용하면 시그널과 함께 이러한 부가 정보들까지 함께 전달 받는다. 다음은 siginfo_t멤버들에 대한 상세 설명이다.

si_signo
시그널 번호이다. 이 시그널 번호는 시그널핸들러에도 동일하게 전달된다.

si_errno
errno값

si_code
시그널을 받았을 때 어떤이유로 시그널이 발생했는지 관련된 값이다.

표 1. SI_CODE 종류

값 설명
SI_ASYNCIO 소켓으로 비동기 입출력 이벤트 발생, 가장 관심있어 하는 시그널이다.
SI_QUEUE sigqueue()함수를 통한 시그널 발생
SI_TIMER 시간 초과
SI_USER kill()함수등에 의한 시그널 발생


si_pid
시그널을 발생시킨 프로세스의 아이디(PID)

si_uid
시그널을 발생시킨 프로세스의 UID로 si_code가 SI_USER일 경우에만 값이 설정된다.

si_status
자식 프로세스에서 SIGCHLD시그널이 발생시키고 종료했을 경우 자식 프로세스의 종료값

si_value
sigqueue()함수를 이용해서 시그널을 발생시킬 경우 사용자가 보낸 값이 저장되어 있다.

typedef union sigval
{
int sival_int;
void *sival_ptr;
} sigval_t;



si_addr
메모리 참조주소의 포인터를 포함한다. 이것은 SIGSEGV, SIGBUS, SIGILL, SIGFPE 등이 발생했을 때만 적용된다.

si_fd
이벤트를 발생시킨 파일지정자.


■표준 시그널은 시그널의 대기열을 유지할 수 없다. 만약 시그널핸들러가 리턴되기전에 여러개의 동일한 시그널이 전달된다면 그 중 하나의 시그널만 전달될 뿐이다. 나머지 시그널은 잃어 버린다. 반면 RTS는 시그널의 대기열을 유지할 수 있으므로, 동시에 여러개의 시그널이 전달된다고 하더라도 이들을 대기열에 담아둘 수 있다.



--------------------------------------------------------------------------------

3.3절. RTS 대기열의 크기
아직 테스트 해보진 않았지만 이론적으로나마 RTS는 대기열을 가질 수 있다고 배웠다. 그렇다면 RTS대기열의 크기가 어느정도 인지 궁금할 것이다. 만약 RTS대기열의 크기가 충분히 크지 않다면, 바쁜서버의 경우 빠른시간에 대기열이 가득 차버리는 문제가 발생할 수도 있기 때문이다. 이러한 문제에 대해서는 다음 기사에서 다루도록 하겠다.


--------------------------------------------------------------------------------

3.4절. RTS와 poll과의 비교
앞장에서 poll에 대해서 간단히 살펴보았다. poll과 RTS모두 이벤트를 받아서 처리한다는 점에서는 매우 비슷하지만 성능에서는 많은 차이가 난다. poll에서는 빈번한 데이터 복사가 일어나며 이벤트가 발생한 파일을 찾기 위해서 열린 파일을 모두 검사해야 하기 때문이다. 열린 파일이 열몇개 정도라면 별문제 없겠지만 수백에서 천에 육박하게 되면 이벤트가 발생한 파일을 찾는데 드는 비용도 결코 무시할 수 없게 된다.

RTS는 이벤트 통지시 어떤 파일에 이벤트가 발생했는지에 대한 정보까지 되돌려 주므로 부가적인 작업없이 해당파일을 통한 작업이 가능하다.

그림 1. poll과 RTS에서의 파일 이벤트 체크





--------------------------------------------------------------------------------

3.5절. RTS지원 확인
최신의 대부분의 유닉스 운영체제들은 RTS를 지원한다. RTS를 지원하는지 확인하는 가장 확실한 방법은 kill명령을 이용해서 커널에서 지원하는 시그널 목록을 확인하는 방법이다.

[root@joinc /root]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10
54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6
58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2
62) SIGRTMAX-1 63) SIGRTMAX

1-31까지는 유닉스 표준 시그널을 위해 예약된 영역이며, 32번 부터 63번 까지가 RTS를 위해 예약된 영역이다. RTS를 위해 예약된 시그널의 갯수는 운영체제마다 약간씩 틀리다. RTS를 사용하기 원한다면 이들 준비된 시그널중 하나를 사용하면 된다.


--------------------------------------------------------------------------------

3.6절. RTS를 이용한 네트워크 입출력 처리
그럼 RTS를 이용해서 소켓에서 발생한 이벤트를 통지 받는 방법에 대해서 알아 보도록 하자.

가장 먼저 해야 할일은 소켓파일이 RTS에 반응하도록 설정하는 일이다. 이것은 파일특성조작 함수인 fcntl(2)을 통해서 이루어진다. fcntl()함수를 이용 해당 소켓을 논블럭,비동기 모드로 작동하도록 세팅한후, 시그널 번호가 SIGRTMIN보다 클경우 해당 소켓으로 전달되도록 세팅한다.

int sockfd = accept(..);

// 소켓을 논블럭,비동기로 설정한다.
fcntl(sockfd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC);
// SIGRTMIN보다 더 큰 RTS시그널이 전달되도록 한다.
fcntl(sockfd, F_SETSIG, SIGRTMIN);
// 시그널을 보낼 프로세스 ID를 설정한다.
// 여기에서는 자기 프로세스로 보내도록 했다.
fcntl(sockfd, F_SETOWN, getpid());
fcntl(sockfd, F_SETAUXFL, O_ONESIGFD);

소켓에 RTS가 통지되도록 했다면 소켓에 RTS가 통지되었을 때 필요한 작업을 하도록 코드를 추가하면 된다. 일단은 RTS가 통지되었는지 확인하는 함수가 필요할 것이다. 유닉스는 sigwaitinfo()와 sigtimedwait()함수를 제공하며, 이 함수들을 이용해서 RTS통지를 확인할 수 있다. #include

int sigwaitinfo(const sigset_t *set, siginfo_t *info);
int sigtimedwait(const sigset_t *set, siginfo_t *info, const
struct timespec *timeout);
int sigqueue(pid_tpid, int sig, const union sigval value);

set은 기다릴 시그널정보가 설정되는 구조체이며, 시그널이 통지 되면 해당 정보가 info에 복사된다. sigtimewait()는 기다리는 시간을 설정할 수 있다는 점을 제외하고는 sigwaitinfo()와 완전히 동일 하다. 다음은 이들 함수를 통해서 시그널을 받고 필요한 일을 처리하는 전형적인 코드의 모습을 보여준다. sigset_t signalset;
siginfo_t siginfo;
int signum, sockfd, revents;

sigemptyset(&signalset);
sigaddset(&signalset, SIGRTMIN);

signum = sigwaitinfo (&signalset, &siginfo);
if (signum == SIGRTMIN)
{
sockfd = siginfo.si_fd;
revents = siginfo.si_band;
// sockfd와 revents를 이용해서 필요한 작업을 한다.
}

다른 프로세스로 (nonrealtime)시그널을 보내기 위해서 kill(2)을 사용할 수 있는 것처럼 RTS를 다른 프로세스로 보낼 수 있는데, 이때 사용하는 함수가 sigqueue(2)이다. 보내는 측에서는 3번째 인자인 sgval를 통해서 부가적인 정보까지 함께 전송할 수 있다. 이 점을 이용하면 IPC용도로도 사용 가능할 것이다. 3.8절에서 자세히 다루고 있으니 참고하기 바란다.


--------------------------------------------------------------------------------

3.7절. RTS 네트워크 예제 작성
그럼 RTS예제를 만들어 보도록 하겠다. 지금까지는 RTS의 장점에 대해서만 얘기 했었는데, RTS에도 한가지 단점이 있는데, 그것은 제대로 다루려면 꽤 복잡한 코딩 과정을 거쳐야 한다는 점이다. 이런 이유로 제대로된 RTS응용 프로그램을 작성하려면 꽤나 많은 신경써야 할것들이 존재한다.

이번예제는 이러한 복잡한 과정을 제외하고 RTS의 기능을 맛보고 테스트할 수 있는 간단한 응용으로 할 것이다.

만들고자 하는 프로그램은 UDP 프로그래밍의 기초에서 다루었던 덧셈연산 서버 프로그램을 RTS를 이용해서 작동하도록 재작성하도록 할 것이다. 클라이언트 프로그램은 그대로 재사용 하도록 하겠다. 다만 여기서 제작하는 서버는 RTS의 특성을 확인하기 위해서 2개의 포트를 만들것이다. 그러므로 클라이언트 프로그램역시 서로 다른 포트로 접근할 수 있도록 약간의 수정을 해주어야 한다. 포트번호를 인자로 받아서 처리하도록 수정해 주기바란다.

코드는 단순하지만 제대로 이해하기 위해서는 시그널과 네트워크 프로그래밍에 대한 기본적으로 이해하고 있어야 한다. 이런 기본적인 내용은 이해하고 있다고 가정하고 대부분의 설명은 주석으로 대신하도록 하겠다.

예제 : sum_server_rts.c

#include
#include
#include
#include
#include
#include

// F_SETSIG의 이용을 위해서 __USE_GNU를 디파인 한다.
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include

/*
* 클라이언트와 통신에 사용할 데이터
*/
struct data
{
int a;
int b;
int sum;
};

/*
* 인자로 주어진 파일 지정자에 대허서
* 비봉쇄(NONBLOCK), 비동기(ASYNC)로 지정하고
* 리얼타임 시그널(SIGRTMIN)에 대응하도록 작업한다.
*/
int setup_sigio(int fd)
{
if (fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC) < 0)
{
printf("Couldn't setup nonblocking io %d\n", fd);
return -1;
}
if (fcntl(fd, F_SETSIG, SIGRTMIN) < 0)
{
printf("Couldn't set signal %d on %d\n", SIGRTMIN, fd);
return -1;
}
if (fcntl(fd, F_SETOWN, getpid()) < 0)
{
printf("Couldn't set owner %d on %d\n", getpid(), fd);
return -1;
}
return 0;
}

/*
* setup_sigio()에 대한 포장함수
*/
void setup_sigio_listeners(fd)
{
if (setup_sigio(fd) != 0)
{
printf("setup_sigio_listners error : %d\n", fd);
exit(0);
}
else
{
}
}

/*
* 해당 포트를 이용해서
* UDP소켓을 작성하고
* 소켓 지정자를 리턴한다.
*/
int get_listener_fd(int port)
{
int sockfd;
int clilen;
int state;
struct sockaddr_in serveraddr;

clilen = sizeof(serveraddr);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
printf("Socket create error\n");
exit(0);
}
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(port);

state = bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (state == -1)
{
perror("bind error : ");
exit(0);
}

return sockfd;
}

int main()
{
struct siginfo si;
sigset_t set;
int ret;
int resockfd;
int sockfd1, sockfd2;
struct data add_data;
struct sockaddr_in clientaddr;
int clilen;

// 1.
// 리얼 타임시그널에 대해서 SIG_BLOCK로
// 작동하도록 설정한다.
sigemptyset(&set);
sigaddset(&set, SIGRTMIN);
sigprocmask(SIG_BLOCK, &set, NULL);

// 2.
// 포트번호 1234, 1235로 2개의 UDP 소켓을
// 만들고 이들 소켓이 RTS에 대응하도록 한다.
sockfd1 = get_listener_fd(1234);
setup_sigio_listeners(sockfd1);

sockfd2 = get_listener_fd(1235);
setup_sigio_listeners(sockfd2);

while(1)
{
clilen = sizeof(clientaddr);

// 3. RTS를 기다린다.
printf("Sig wait\n");
ret = sigwaitinfo(&set, &si);

// 4.
// 만약 RTS가 도착했다면
// siginfo구조체의 값을 검사해서 어느 소켓으로 부터
// 데이터가 왔는지 확인하고, 해당 소켓을 통해
// 클라이언트와 통신한다.
if(ret == SIGRTMIN)
{
// select를 쓰지 않고도 이벤트가 발생한
// 소켓을 알아낼 수 있다.
printf("=========================\n");
printf("RTS I/O socket %d\n", si.si_fd);
printf("RTS I/O revents %d\n", si.si_band);
printf("=========================\n");
resockfd = si.si_fd;
recvfrom(resockfd, (void *)&add_data, sizeof(add_data), 0,
(struct sockaddr *)&clientaddr, &clilen);
add_data.sum = add_data.a + add_data.b;
sendto(resockfd, (void *)&add_data, sizeof(add_data), 0,
(struct sockaddr *)&clientaddr, clilen);
// 5.
// 디버깅용 : 필요에 따라 주석을 풀고 테스트 해본다.
// printf("sleep\n");
// sleep(10);
}
}
}




1.에서 RTS를 사용하도록 세팅한다. sigaddset()를 이용해서 RTS를 대응하도록 설정한다. 그후 sigprocmask()를 이용해서 동일한 RTS가 들어왔을 경우 인터럽트가 걸리지 않고 블럭되도록 설정한다. 만약 sigprocmask()를 이용해서 RTS를 블럭하지 않는다면 sigwaitinfo()가 호출되어서 RTS를 기다리기 전에 RTS가 프로세스로 전달될경우 프로세스에 인터럽트가 걸리고 프로세스는 종료되어 버릴 것이다.

유닉스 표준 시그널에서는 시그널이 블럭될 경우 하나의 시그널만 유지하고 나머지 시그널은 모두 잃어 버리지만 RTS는 블럭되더라도 시그널의 열을 유지한다. 실제 유지되는지는 잠시 후에 테스트 해보도록 하겠다.

2.에서 2개의 UDP소켓을 만들어서 각각의 소켓에 대해서 setup_sigio_listeners()를 이용해서 RTS에 대응하도록 만들었다. 파일에 대한 RTS대응에는 fcntl()이 매우 중요한 역할을 한다.

3.에서 sigwaitinfo()를 이용해서 RTS를 기다린다. 만약 UDP소켓에 이벤트가 발생하면 RTS가 전달 되고, sigwaitinfo()는 리턴하게 된다. 리턴할때 2번째 인자인 si(siginfo)를 채워주게 되는데, siginfo에는 이벤트 발생한 파일과 이벤트 정보등이 담겨져 있다.

4.에서 siginfo구조체의 내용을 이용해서 어느 소켓으로 어떤 이벤트가 발생했는지 확인할 수 있으며, recvfrom(), sendto()함수를 이용해서 데이터 통신을 하면 된다.

5.는 디버깅용이다. sleep()를 걸어 놓고 10초 사이에 2번 이상 클라이언트를 이용해서 데이터 통신 테스트를 해보면 시그널정보가 대기열에 쌓이는 것을 확실히 확인할 수 있을 것이다.


--------------------------------------------------------------------------------

3.8절. 프로세스간 신호전달
RTS는 파일에 대한 이벤트 전달을 위한 좋은 도구이며, 실제로 거의 대부분 네트워크 프로그래밍을 위한 도구로 사용되지만 프로세스간 신호 전달을 위한 목적으로도 사용할 수 있다.

프로세스간 신호전달용으로 사용할 때 얻을 수 있는 장점은 시그널이 대기열에 쌓이므로 잃어버릴 염려가 없다는 점과 부가적인 정보를 전달할 수도 있다는 점이다. 다른 프로세스로의 RTS전달은 sigqueue(2)함수를 이용한다.

부가적인 정보의 전달은 sifinfo_t구조체의 sigvalue를 통해서 이루어진다. sigvalue는 다음과 같은 멤버를 가진다.

union sigval
{
int sival_int;
void *sival_ptr;
}

sival_int는 int형 값을 전달하기 위해서 사용된다. 메뉴얼을 보면 sival_ptr의 경우 주소값을 전달하기 위해서 사용한다고 되어있는데, 실제 어디에 사용가능한지 확인 할 수 없었다. 아는 사람이 있으면 댓글을 달아주기 바란다.

RTS에 대응하도록 애플리케이션을 만드는 방법은 일반 유닉스 표준 시그널을 다루는 프로그램과 크게 다를바 없다. sigaction의 sa_flags를 SA_SIGINFO로 설정하고 적당한 시그널 핸들러를 등록하기만 하면 된다. 그리고 RTS가 전달되었을 경우 si_code가 SI_QUEUE인지를 확인하고 원하는 작업을 하면 된다. SI_QUEUE인지를 확인하는 이유는 RTS가 아닌 표준 시그널이 도착할 수 있고, 이를 구별해서 작업해야할 필요가 있기 때문이다. 다음은 RTS에 반응하는 애플리케이션이다.

예제 : rcv_rts.c

#include
#include
#include

/*
* 시그널 핸들러
* si_code가 SI_QUEUE 인지를 확인한후 원하는 작업을 한다.
* SI_QUEUE일 경우 RTS형식으로 전달된 시그널이며
* 그렇지 않을경우 표준 유닉스 시그널이다.
*/
void sighandler(int signo, siginfo_t *si)
{
if(si->si_code == SI_QUEUE)
{
printf("User RTS signal %d\n", si->si_pid);
printf("Sig Number %d\n", si->si_signo);
printf("User Data is %d\n", si->si_value.sival_int);
// 시그널이 큐잉되는지 확인하기 위한 코드
getchar();
}
else
{
// kill등을 이용해서 표준 유닉스 시그널을 보냈을 경우
// 실행되는 루틴
printf("Get none realtime signal %d\n", signo);
}
}

int main()
{
struct sigaction sigact;

printf("My pid %d\n", getpid());

/*
* sa_flags를 SA_SIGINFO로 설정하고
* 시그널 핸들러를 등록한다.
*/
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_SIGINFO;
sigact.sa_restorer = NULL;
sigact.sa_sigaction = sighandler;

/*
* RTS시그널에 대한 핸들러를 설치한다.
*/
if (sigaction(SIGRTMIN, &sigact, 0) == 1)
{
printf("signal error\n");
exit(0);
}
while(1)
{
sleep(1);
}
}



다음은 RTS를 발생시키는 예제 프로그램이다.

예제 : snd_rts.c

#include
#include

/*
* argv[1]은 보내고자 하는 프로세스의 PID
* argv[2]는 보내고자 하는 값이다.
* SIGUSR1을 RTS로 전송한다.
*/
int main(int argc, char **argv)
{
union sigval sv;

sv.sival_int = atoi(argv[2]);
sigqueue(atoi(argv[1]), SIGRTMIN, sv);
}

이제 rcv_rts를 띄우고 나서 snd_rts로 테스트 해보기 바란다. snd_rts로 테스트 했을경우 RTS를 받는걸 확인 할 수 있다. rcv_rts에서 키보드입력이 없다면 getchar()에서 블럭될 건데, 이때 snd_rts를 여러번 실행하면 시그널이 대기열에 쌓이는 특성도 확인 할 수 있다.

그렇지 않고 kill을 이용해서 표준 유닉스 시그널을 보낼수도 있는데, 그럴 경우 표준 유닉스 시그널 처리 루틴으로 넘어가는걸 확인할 수 있을 것이다.


--------------------------------------------------------------------------------

4절. 결론
이상 RTS에 대해서 간단히 알아보았다. 여기에서 다룬내용은 말그대로 RTS의 개념이해를 위한 맛보기일 뿐이다. 실제 RTS를 응용한 네트워크 프로그래밍의 작성에는 신경써줘야 할 많은 문제들이 있다. 이러한 내용들은 다음 문서를 통해 다루도록 할것이다.

Linux sigqueue - 대기열 기반 시그널

Linux sigqueue - 대기열 기반 시그널

1장. sigqueue(2)차례
1.1절. 사용법
1.2절. 설명
1.3절. 반환값
1.4절. 에러
1.5절. 예제
대기열 기반 시그널및 데이터 전송


--------------------------------------------------------------------------------

1.1절. 사용법

#include

int sigqueue(pid_t pid, int sig, const union sigval value);




--------------------------------------------------------------------------------

1.2절. 설명
sigqueue는 지정된 시그널번호 sig를 PID가 pid인 프로세스 에게 보낸다.


--------------------------------------------------------------------------------

1.3절. 반환값
성공할경우 0을 실패했을경우에는 -1을 반환하며, 적당한 errno 값을 설정한다. kill(2)과 매우 비슷하게 작동하며, kill(2)과 마찬가지로 null 시그널을 이용해서 프로세스가 존재하는지 확인하기 위한 목적으로 사용할 수도 있다.

value를 이용해서 시그널과 함께 필요한 데이터를 함께 전송할수도 있다. value는 다음과 같은 타입을 가진다.

union sigval
{
int sival_int;
void *sival_ptr;
};

만약 시그널과 함께 데이터를 전송 받기를 원한다면 sigaction(2)을 이용해서 SA_SIGINFO플레그를 설정하고 시그널핸들러의 두번째 인자를 siginfo_t로 설정하면 된다. 받는측 에서는 대기열 기반 시그널인지 확인을 위해서 siginfo_t의 si_code 가 SI_QUEUE인지를 검사하면 된다.


--------------------------------------------------------------------------------

1.4절. 에러


EINVAL
잘못된 시그널

EPERM
신호를 받을 프로세스에 대한 권한이 없다.

ESRCH
신호를 받을 프로세스의 PID가 존재하지 않는다.



--------------------------------------------------------------------------------

1.5절. 예제

#include

#include
#include

/*
* 시그널 핸들러
* si_code가 SI_QUEUE 인지를 확인한후 원하는 작업을 한다.
* SI_QUEUE일 경우 RTS형식으로 전달된 시그널이며
* 그렇지 않을경우 표준 유닉스 시그널이다.
*/
void sighandler(int signo, siginfo_t *si)
{
if(si->si_code == SI_QUEUE)
{
printf("User RTS signal %d\n", si->si_pid);
printf("Sig Number %d\n", si->si_signo);
printf("User Data is %d\n", si->si_value.sival_int);
// 시그널이 큐잉되는지 확인하기 위한 코드
getchar();
}
else
{
// kill등을 이용해서 표준 유닉스 시그널을 보냈을 경우
// 실행되는 루틴
printf("Get none realtime signal %d\n", signo);
}
}

int main()
{
struct sigaction sigact;

printf("My pid %d\n", getpid());

/*
* sa_flags를 SA_SIGINFO로 설정하고
* 시그널 핸들러를 등록한다.
*/
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_SIGINFO;
sigact.sa_restorer = NULL;
sigact.sa_sigaction = sighandler;

/*
* RTS시그널에 대한 핸들러를 설치한다.
*/
if (sigaction(SIGRTMIN, &sigact, 0) == 1)
{
printf("signal error\n");
exit(0);
}
while(1)
{
sleep(1);
}
}

위 예제는 시그널을 받는 예제 프로그램으로 대기열 기반의 리얼타임 시그널에 대응한다. 프로그램 작동방식과 예제에 대한 자세한 설명은 Real Time Signal - 1을 참고하기 바란다.

리눅스 시스템 프로그래밍 6장 - Signal

리눅스 시스템 프로그래밍 6장 - Signal

Contents
1 signal의 일반적 정의
1.1 시그널
1.2 메시지
2 운영체제에서의 signal
3 shell에서 signal의 사용
4 시그널을 받았을 때
5 시그널의 범주
6 시그널 프로그래밍
6.1 kill 함수를 이용한 시그널 보내기
6.2 signal 함수를 이용한 시그널 catch
7 시그널의 특징
7.1 대기열을 가지지 않는다.
7.2 비신뢰성
8 signal의 제어
8.1 kill을 이용한 시그널의 전송
8.2 signal를 이용한 비동기적 시그널 처리
8.3 signal을 이용한 동기적 시그널 처리
9 sigaction 함수군 을 이용한 시그널 객체의 처리
10 마치며


1 signal의 일반적 정의 의미를 전달하기 위해서 사용하는 일반적인 방법으로 신호와 메시지가 있다. 메시지는 언어기반의 전달방식이며, 여러의미를 내포하고 있는 비교적 복잡한 의미전달 방식이다.


반면, 신호는 하나의 의미만을 내포한다. 해석이 간단하고 빠른전달이 가능하다는 장점을 지난다. 예를들어 교통신호 표지판은 각각이 하나의 의미만을 가지며, 누구에게나 동일하게 해석이 된다.


이상은 일반 생활에서의 신호와 메시지의 차이점을 기술한건데, 컴퓨터에서의 의미전달에도 그대로 적용된다.


1.1 시그널 시그널 즉 신호는 "의미"를 상대방에게 전달하기 위해서 사용하는 소통방식 중 하나로, 아마도 인류의 가장오래된 소통방식 중 하나일 것이다. 하긴 signal은 인간만이 사용할 수 있는 유일한 것은 아니다. 시그널은 그 자체가 복합적이고 추상적인 언어의 성격보다는 구체적이고 단순한, 즉 1:1로 의미가 매칭이 되는 단순한 소통의 방식이기 때문이다. 예컨데, 동물들도 울음소리나 몸동작, 호르몬, 배설물들을 이용해서 다른 동물들에게 신호를 보낸다.


signal이 동물들과 인간들에게 있어서의 최소한의 의사소통으로 오랫동안 선호되어온 이유는 그 사용방법의 간단함과 의미전달의 효율에 있을 것이다. 언어의 경우에는 매우 간단한 문장이라고 하더라도 다양한 해석이 있을 수 있고, 문화와 환경이 다를 경우에는 전혀 다른 해석이 있을 수 있지만, 정보와 의미가 1:1로 매칭되는 signal은 그 의미를 해석하는데 많은 에너지를 사용할 필요가 없기 때문이다. 빠르고 전달되고 빠르게 해석된다는 장점을 가진다고 볼 수 있겠다.


물론 signal도 단점은 있다. 1:1이기 때문에, 생존에 필요한 최소한의 정보를 정확하게 전달하기에는 매우 효율적이지만, 복잡한 의미를 전달할때에는 오히려 효율이 떨어질 수 있다는 점이다.


요즘과 같이 복잡한 시대에, 신호와 같은 단순한 것들로는 살아가기가 귀찮을 것 같다고 생각될지도 모르지만, 사실 신호는 오히려 더욱더 널리 사용되는 추세다. 그렇잖아도 복잡한 세상인데, 어느 세월에 그걸 말과 글로 설명하고 앉아 있을 것인가.


지금은 위험하오니 길을 건너지 마시오하는 것보다 빨간불켜주는게 의미를 훨씬더 잘 전달할 수 있다. 신호는 오히려 차고 넘친다. 도로는 온통 신호들로 넘쳐나며, 인터넷 세계역시 이모티콘으로 대표되는 신호들로 가득 채워져 있다.


이들 신호는 주로 다음의 두가지 용도에 특히 잘 사용될 수 있다.

1.비동기적인 사건이 발생했음을 알리기 위함 : 즉 예로 들자면 전화벨, 메시지가 도착했음을 알리는 알람

2.사건을 동기화 하기 위함 : 시계알람, 자동차경주에서 출발시각을 맞추기 위함

1.2 메시지 시그널은 빠르고, 간단하게 이해될 수 있다는 장점이 있지만 복잡한 정보를 전달할 수 없다는 단점을 가진다. 그래서 인간의 언어와 비슷한 형태로 메시지를 이용해서 정보를 전달하는 방법도 있다.


이 방식을 이용하면 복잡한 정보를 전달할 수 있지만, 메시지의 형식과 해석방법에 대해서 서로 약속이 되어 있어야 한다. 나는 너를 사랑한다라는 인간의 메시지를 예로 들어보자. 이 메시지를 정확히 전달하고 이해할려면 주어+동사+목적어라는 문장의 형식과 나, 너, 사랑, 하다의 단어들의 의미를 알고 있어야만 한다. 또한 이러한 약속을 알고 있다고 하더라도, 자신이 처한 문화/사회적 환경과 교육수준에 따라서 전혀 다르게 해석이 되기도 한다.


이처럼 이용하기 복잡하지만 형식과 의미를 이해하고 있다면, 몇개의 단어만을 가지고도 엄청나게 다양한 정보전달이 가능하다는 장점을 가진다. 우리가 일상적으로 사용하는 단어는 수천개에 불과하다. 그렇지만 이 수천개로 거의 무한에 가까운 의미를 전달할 수 있다.


컴퓨터에서는 프로세스간 복잡한 정보교환을 위해서 메시지를 교환하는 경우가 많다. 인간의 언어와 마찬가지로 이들도 해석될 수 있도록 메시지 규약을 가지고 있어야 한다. 이러한 메시지 규약을 Protocol이라고 한다. 예를들어 Web Server는 Web Client와 메시지 통신을 하는데, 이때 따르는 Protocol이 HTTP(HyperText Transfer Protocol)이다. 마찬가지로 파일전송을 위한 일반적인 프로토콜로 FTP가 있다.



이들 메시지는 내부 프로세스 간의 통신일 경우 IPC라고 하는 리눅스 운영체제가 제공하는 내부통신메커니즘에 따라서 전달될 수 있다. 멀리떨어진 프로세스, 즉 인터넷으로 연결된 프로세스들이라면 TCP/IP(:12)를 이용해서 통신이 이루어질 것이다. TCP/IP는 인터넷상에서 데이터를 전송하기 위한 프로토콜이며, 리눅스는 이들 프로토콜을 쉽게 사용할 수 있도록 socket를 제공한다.


IPC는 뒷장에서 따로 다룰 것이다. 네트워크상에서 socket을 이용한 프로세스간 통신은 이 문서의 범위를 벗어난다. 아마도 네트워크 프로그래밍 관련된 별도의 문서를 통해서 다루게 될 것이다.


2 운영체제에서의 signal 운영체제가 하는 가장 중요한 일은 컴퓨터와 인간이 서로 원할히 소통할 수 있게끔 도와주는 일이 될것이다. 운영체제와 인간 사이에는 다시 응용 소프트웨어가 놓여있고, 인간은 응용 소프트웨어를 통해서 운영체제와 소통을 하게 된다.

+----------+ | |
| Computer |<--->| OS | <-------> 응용 APP <---------> 사용자
| | | | <-------> 응용 APP
| | | | <-------> 응용 APP
+----------+ | |


운영체제는 이들 응용 소프트웨어간 그리고 응용 소프트웨어와 사용자간의 소통을 위한 몇가지 도구들을 제공한다. 예컨데, IPC(다음 장에서 다루게될)와 같은 것들인데, 이것은 인간의 언어의 형태에 가깝다. 즉 다양한 형태로 해석될 수 있는 메시지들을 주고 받음으로써, 각각의 객체들간의 소통을 지원한다.


이들은 재해석가능한 데이터를 이용하기 때문에, 객체간 복잡한 소통을 할 수 있지만, 반대로 사용하기에 너무 복잡한 면이 있다. 때로는 인간이나 혹은 동물들이 그렇듯이, 아주 간단하게 소통할 수 있는 signal같은 도구도 필요할 것이다.


그래서 대부분의 운영체제는 signal을 지원하며, 마찬가지로 Linux도 signal을 지원한다. 시그널은 인간이나 동물이 사용하는 그것과 매우 유사하다. 즉 의미와 정보가 1:1로 매칭되기 때문에, 재해석할 필요없이 즉시 의미를 알아낼 수 있도록 되어 있다.


리눅스 운영체제는 미리 약속되어 있는 수십가지의 signal을 제공하는데, kill(1)을 이용하면 리눅스가 지원하는 signal의 종류를 알아낼 수 있다.

# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4
39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6
59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
여기에서 시그널의 이름과 그 시그널의 고유번호를 알 수 있다.


상당히 많은 시그널이 있는데, 자주사용되는 것들을 정리해 보자면 다음과 같다. 각각의 시그널은 고유한 단일의 의미를 가지고 있음을 알 수 있을 것이다.

SIGKILL 프로세스를 죽여라
SIGALARM 알람을 발생한다
SIGSTP 프로세스를 멈춰라
SIGCONT 멈춰진 프로세스를 움직이게 하라
SIGINT 프로세스에 인터럽트하라. 즉 차단하라
SIGSEGV 프로세스가 다른 메모리영역을 침범했다.

이들 시그널의 사용용도는 비동기적인 사건을 전달하는데, 특히 유용하게 사용할 수 있는 소통의 도구다. 즉 프로세스를 죽이는 것은 그 시간을 알 수 없는 비동기적인 사건이다. 프로세스가 다른 메모리영역을 침범한 것 역시, 그 발생시간을 알 수가 없는 비동기적인 사건임을 알 수 있다.


이들 시그널은 일상생활에서 사용하는 시그널과 마찬가지로 비동기적인 사건의 발생을 통지하기 위한 용도와 사건을 동기화 시키기 위한용도로 크게 나눌 수 있다. 위의 경우를 예로 들어서 설명해 보자면, SIGALARM은 사건을 동기화 시키기 위해서, SIGKILL, SIGSEGV는 비동기적인 사건을 통지하기 위해서 사용을 한다.


3 shell에서 signal의 사용 shell 프로그램을 다른 프로세스에 signal을 전달할 수 있는 kill(1)이라는 프로그램을 제공한다. 사용방법은 다음과 같다.

kill -signal pid
예를들어 pid가 100인 프로세스를 죽이고 싶다면, 아래와 같이 kill을 사용하면 된다.

# kill -SIGKILL 100
혹은 시그널 이름대신에 시그널의 고유번호를 사용하는 방법도 있다.

#kill -9 100
이제 100번 프로세스로 SIGKILL 시그널이 전달되고, 해당 프로세스는 강제로 종료가 될 것이다. 물론, 그렇다고 해서 아무 프로세스나 마구 시그널을 보낼 수 있는 것은 아니다. 자기가 권한을 가지고 있는 프로세스에 대해서만 시그널을 보낼 수 있다. 아무 프로세스에 관계없이 시그널을 보내기 위해서는 root 권한을 가져야만 한다.


또한 키보드 입력으로 시그널을 발생시킬 수도 있다. 가장 대표적인게, 프로그램을 종료시키기 위해서 Ctrl+C 입력하는 것으로, 이 키를 입력하면 해당 프로세스에 SIGINT가 전달이 되어서 프로세스가 종료가 된다. 앞에서 SIGINT는 프로세스의 중단이라고 했는데, 왜 종료가 되는건지가 의문점으로 남을 것이다. 이에 대해서는 다음절에서 설명하도록 할 것이다. Ctrl+C를 입력했을 때, 프로그램이 종료되는건 어렵지 않게 확인할 수 있을 것이다. 아래의 프로그램으로 확인해 보도록 하자. 프로그램이름은 sigtest.c 로 하겠다.

#include
#include
#include

int main(int argc, char **argv)
{
int i = 0;
while(1)
{
printf("%d\n", i);
i++;
}
}


키보드 입력으로 발생시킬 수 있는 시그널은 Ctrl+C 외에도 아래의 몇가지가 있다.

Ctrl+C SIGINT 프로세스를 종료시킨다.
Ctrl+Z SIGSTP 프로세스를 중단시킨다.
Ctrl+\ SIGQUIT core dump를 남기고 프로세스를 종료시킨다.



4 시그널을 받았을 때 시그널은 고유의 의미를 내포하고 있다. 이러한 시그널을 받은 실행객체인 프로세스는 그에 맞는 행동을 해야 한다. 시그널을 받은 프로세스는 다음중 한가지 행동을 취해야 한다.

1.그 시그널을 처리할 등록된 함수를 호출한다.

2.시그널을 무시한다.

3.시그널을 무시하지 않지만, 그렇다고 해서 특별히 함수를 호출하지도 않는다.

전화벨이 울리면 전화를 받거나 무시하거나 할 수 있을 것이다. 각각 1, 2번에 해당한다.


만약 시그널을 무시하지도 않고, 호출할 함수도 등록하지 않았다면 시그널에 대한 기본행동을 취하게 된다. 이 기본행동에는 다음과 같은 것들이 있다.

1.프로세스가 죽는다. 대부분의 시그널에 대한 기본행동

2.프로세스가 중단된다.

3.아무일 없이 지나간다. - 무시된다.

예를들어 CTRL+C 을 입력하면, SIGINT 신호가 발생되는데, 시그널에 대한 행동을 정하지 않았다면, 기본행동인 프로세스 종료를 취하게 된다.


시그널에 대해서, 호출될 함수를 등록하건나 혹은 무시하게 만드는 것은 뒤에 따로 다루도록 하겠다.


5 시그널의 범주 시그널은 제어가능한 시그널과 그렇지 못한 시그널이 있다. 여기에서 제어는 시그널을 catch하는 것에서 부터 시작된다. catch하지 못한다면 (당연히)제어할 수도 없다.


대부분의 시그널은 catch가 가능하며, 무시하거나, 기본행동을 하게 하거나, 별도의 함수를 실행시킬 수 있다. 그러나 catch 불가능한 함수가 있는데, SIGKILL과 같은 함수가 대표적이다.


SIGKILL은 프로세스를 무조건 죽이기 위해서 사용하는데, 예컨데 오동작하면서 CPU자원을 무한대로 소비하는 프로세스라면 강제로 종료시켜야 할 것이다. 그런데, 시그널이 catch 되어서 시그널을 무시하게 해버린다면, 시스템 관리자로서는 상당히 난감할 것이다. 그러므로 이 프로세스는 catch하지 못하도록 하고 있다.


SIGSTOP도 마찬가지로 catch할 수 없다. SIGSTOP를 받은 프로세스는 즉시 중단되어야 하며, 무시할 수 없다. 역시 시스템 관리를 위한 목적으로 사용할 수 있을 것이다.


6 시그널 프로그래밍 시그널이 전달되면, 프로세스는 무시하던지, 지정된 함수를 호출하든지 해야 한다. 그렇지 않다면 기본행동을 취하게 된다. 여기에서는 시그널을 제어하는 방법 즉, 시그널을 무시하거나 호출된 함수를 지정하는 방법에 대해서 알아보도록 할 것이다.


6.1 kill 함수를 이용한 시그널 보내기 kill은 프로세스에 시그널을 전달하기 위해서 사용할 수 있는 가장간단한 함수로, 다음과 같이 선언되어 있다.

#include
#include

int kill(pid_t pid, int sig);
■pid : 프로세스의 PID

■sig : 시그널 번호


연습삼아서 프로세스에 원하는 시그널을 보내는 간단한 프로그램을 만들어 보도록 하자.

#include
#include
#include
#include

int main(int argc, char **argv)
{
int pid;
int sig_num;

if (argc != 3)
{
printf("usage %s [pid] [signum]\n", argv[0]);
return 1;
}
// 실행인자로 pid 번호와
// 전송할 signal 번호를 받아들여서
// 이를 해당 pid 로 보낸다.
pid = atoi(argv[1]);
sig_num = atoi(argv[2]);
if(!kill(pid, sig_num))
{
perror("Signal Send error");
return 1;
}
return 0;
}
다음은 실행 결과다.

$ ./signal 6653 2
Signal Send error: Success


6.2 signal 함수를 이용한 시그널 catch kill은 다른 프로세스에게 시그널을 보내기만 할뿐, 자신에게 전달되는 시그널을 catch 해서 처리할 수는 없다. 리눅스는 signal 함수를 제공하는 데, 이를 이용해서 자신에게 전달되는 시그널을 처리할 수 있다.


signal 함수는 다음과 같이 선언되어 있다.

#include

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
1.signum : 제어할 시그널 번호

2.sighandler_t : signum을 받았을 때, 호출할 함수


다음은 간단한 예제다. 프로그램이름은 sigint.c로 하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include
#include
#include

void sig_handler(int signo);

int main(int argc, char **argv)
{
int i = 0;
signal(SIGINT, (void *)sig_handler);
while(1)
{
printf("%d\n", i);
sleep(2);
i++;
}
}

void sig_handler(int signo)
{
printf("I Received SIGINT(%d)\n", SIGINT);
}

CTRL+C 를 입력하게 되면, 현재 실행중인 프로세스에 SIGINT가 전달이 된다. SIGINT에 대한 프로세스의 기본행동은 종료이기 때문에, 특별히 시그널을 제어하지 않을 경우 프로그램은 종료가 된다. 그러나 위에서는 signal함수를 이용해서 SIGINT에 대해서 sig_handler라는 함수를 실행하도록 했다. 이제 CTRL+C를 입력하게 되면, 프로세스가 종료되는 대신 sig_handler를 실행하는걸 확인할 수 있을 것이다.


위 예에서는 시그널함수를 실행시키도록 하고 있는데, 시그널을 무시하거나 시그널의 기본행동으로 되돌아가도록 할 수도 있다. 이경우 sig_handler 대신에 SIG_IGN과 SIG_DFL을 이용하면 된다.

■SIG_IGN : 시그널을 무시한다.

■SIG_DFL : 기본행동을 하도록 한다.

위코드의 10번째 줄을 다음과 같이 바꾸고 테스트해보자.

signal(SIGINT, SIG_IGN);
CTRL+C 키가 아예먹지 않는걸 확인할 수 있을 것이다.


그렇다면 SIG_DFL은 언제 사용되는가 ? 별도의 시그널제어 함수를 사용하지 않는다면, 시그널에 대해서 기본행동을 하도록 되어 있는데, 사용할 필요가 있는가 하는 의문이 생길 수 있을 것이다. SIG_DFL은 다음의 두가지 용도로 주로 사용된다.

■최초 시그널을 무시했는데, 중간에 시그널을 기본행동으로 해야할 필요가 있을 때.

■자식프로세스를 생성했을때.

fork(2)를 이용해서 자식프로세스를 생성하면, 자식프로세스는 부모의 시그널정책까지를 그대로 복사해서 사용하게 된다. 즉 부모의 특정 시그널에 정책이 SIG_IGN 이였다면, 자식도 그대로 그 정책을 따른다. 때로, 자식의 시그널 정책을 달리할 필요가 있을 것이다. 이 경우 사용할 수 있다.


7 시그널의 특징 지금까지 해서, 시그널의 간단한 특징과 시그널을 전송하고 받는 것에 대한 기본적인 정보를 얻게 되었다. 이제 본격적으로 시그널의 제어와 관련된 얘기를 해야 할건데, 그 전에 시그널의 세부적인 특징에 대해서 언급하고 넘어가야 할것 같다.


대상이 무엇이든지 간에, 대상을 제대로 제어하기 위해서는 대상의 특징을 우선알고 있어야 할 것이기 때문이다.


7.1 대기열을 가지지 않는다. 시그널의 첫번째 특징은 대기열을 가지지 않는 다는 점이다. 이것은 뭐냐면, 프로세스는 동시에 하나의 시그널만 처리할 수 있다는 얘기가 된다. 예를들어서 SIGINT 시그널을 받아서, 이에 대한 처리를 하고 있는중에, 다시 SIGINT가 발생하게 된다면, 이 시그널은 잃어버리게 된다.


다음은 대기열을 가지지 않는 시그널의 특징을 테스트하기 위한 간단한 프로그램이다.


위의 sigint.c에 한줄추가했을 뿐이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include
#include
#include

void sig_handler(int signo);

int main(int argc, char **argv)
{
int i = 0;
signal(SIGINT, (void *)sig_handler);
while(1)
{
printf("%d\n", i);
sleep(2);
i++;
}
}

void sig_handler(int signo)
{
printf("I Received SIGINT(%d)\n", SIGINT);
sleep(4);
}

23라인에 sleep()함수가 추가되었다. 이제 프로그램을 실행시키고, CTRL+C를 입력하면 SIGINT가 전달될 것이고, 프로세스는 sig_handler을 실행시킬 것이다. sig_handler는 4초를 기다리는데, 이때 CTRL+C를 다다닥 눌러서 한 10번 정도 실행시켜보자.


만약 시그널대기열이 있다면, sig_hanler는 4초의 간격을 두고 10번 실행되어야 하겠지만, 단 한번만 실행되는걸 확인할 수 있을 것이다. 왜냐하면 대기열이 없기 때문에, sig_handler이 실행된 후, 오직 하나의 SIGINT만 접수가 되기 때문이다.


나머지 시그널은 전부 버려진다.


의미를 전달하기 위한 매우 사용하기 편한 방법임에도 불구하고, 시그널의 이러한 특징은 시그널의 사용을 주저하게 만드는 이유가 되기도 한다. 이러한 문제를 해결하기 위해서, 최근에는 시그널이 대기할 수 있는 대기열 매커니즘을 제공하는 RTS(real-time signal)이 사용되기도 한다. RTS에 대한 내용은 이 문서의 후반부에서 따로 다루도록 하겠다.

7.2 비신뢰성 요청에 대한 응답으로 메시지가 전달되었는지 확인하는 쌍방향 통신과는 달리, 시그널은 프로세스에 제대로 전달되었는지 확인할 수 있는 방법이 없다.


신호가 전달되었는지를 신뢰할 수 없기 때문에 비신뢰적인 특징을 가진다고 말한다.


8 signal의 제어 이제 시그널을 제어하는 방법에 대해서 자세히 알아보도록 하겠다. 시그널의 제어는 크게 3가지 부분으로 이루어진다.

1.시그널의 전송

특정 프로세스, 혹은 자식프로세스와 같은 그룹의 프로세스에 시그널을 보내는 방법

2.시그널의 catch

여기에는 시그널을 그룹단위로 처리하거나 특정 시그널을 블럭 하는 등에 대한 내용이 들어간다.

3.시그널의 처리


8.1 kill을 이용한 시그널의 전송 signal을 전송하기 위한 가장 간단한 방법은 kill(2) 시스템함수를 이용하는 것으로, kill의 사용방법은 다음과 같다. - shell의 kill 명령과 혼동하지 말자. -

#include
#include

int kill(pid_t pid, int sig);
pid는 시그널을 받을 프로세스의 pid로 그룹 혹은 특정 pid에 시그널을 보낼 수 있다.

■pid > 0 : pid에 대응되는 프로세스에 시그널을 보낸다.

■pid == 0 : 현재프로세스에 속한 모든 그룹의 프로세스에 시그널을 보낸다.

■pid == -1 : 1번 프로세스 (init) 를 제외한 모든 프로세스에 시그널을 보낸다.

■pid < -1 : pid의 모든 그룹의 프로세스에 signal을 보낸다.


8.2 signal를 이용한 비동기적 시그널 처리 그럼 비동기적으로 발생하는 시그널을 처리하는 방법에 대해서 알아보도록 하겠다. 비동기적이라는 것은 특정 시점에서 시그널이 발생하는 것을 기다리지 않는다는 얘기가 된다. 즉 시간을 일치 시키지 않겠다는 것으로 전화벨이 신호라고 할때, 전화벨이 언제 움직일 지를 알 수 없다. 우리는 전화벨을 기다리지 않는다. 전화벨이 움직이면 반응할 뿐이다. - 물론 기다리는 전화도 있긴 하지만 -


리눅스는 signal(2) 이라는 함수를 제공하는데, 이 함수를 이용해서 비동기적으로 발생하는 시그널에 반응해서 필요한 일을 수행 할수 있다. 다음은 signal 함수의 사용법이다.

#include

void (*signal(int signum, void (*handler) (int))) (int);
1.signum : 제어하고자 하는 시그널 번호

2.handler : 시그널이 발생했을 때, 실행할 함수


다음은 signal 함수를 이용한 간단한 예제 프로그램으로, SIGSTOP가 전달되면 mystop()라는 시그널 등록함수를 실행시킨다.

#include
#include

void mystop(int signo)
{
printf("I Received Signal : %d\n", signo);
}

int main(int argc, char **argv)
{
int i =0;
signal(SIGQUIT, (void *)mystop);

while(1)
{
printf("%d\n", i);
i++;
sleep(1);
}
return 1;
}


8.3 signal을 이용한 동기적 시그널 처리 동기적 시그널 처리라는 것은 우리가 다른 일을 제쳐두고 애인의 전화벨을 기다리는 것처럼, 작업을 중단하고 시그널을 기다리겠다라는 얘기가 된다.


리눅스는 sigwait()함수를 제공한다.

#include

int sigwait(const sigset_t *set, int *sig);
sigwait 는 set에 등록된 시그널이 발생될 때까지 기다린다. 여기에 sigset_t라는 데이터타입이 등장하는데, 여기에는 시그널에 대응되는 bit 값이 설정되어 있다.


sigwait를 다루기 위해서는 시그널을 객체로 다루는 방법에 대한 지식이 필요하다. sigaction 함수를 이용하면 시그널을 객체단위로 다룰 수 있는데, (다음 절에서) sigaction을 다루면서 sigwait의 사용방법에 대해서 설명하도록 하겠다.


9 sigaction 함수군 을 이용한 시그널 객체의 처리 signal 함수는 간단하게 사용할 수 있기는 하지만 시그널을 객체로 보지 않고, 단일 대상으로 본다는 문제점을 가진다. sigaction을 사용하면 시그널을 객체로 다룰 수 있는데, 이 객체에는 다음과 같은 것들이 포함된다.

■시그널 set

■시그널에 대한 정책

■시그널 함수

이러한 요소들을 하나의 객체로 볼 경우 분명히 더 쉽게 시그널을 관리할 수 있을 것이다. 리눅스는 이들을 객체로 다루기 위해서 sigaction() 함수를 제공한다. 이 함수는 struct sigaction을 이용해서 시그널 객체요소를 다룬다.

#include
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
1.signum : 제어할 시그널의 번호

2.act : 시그널을 제어하기위한 정책을 정의할 수 있다. 이 구조체는 다음과 같이 정의되어 있다.



struct sigaction
{
void (*sa_handler) (int);
void (*sa_sigaction) (int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
}
구조체의 각 멤버에 대한 자세한 내용은 sigaction(2) 문서를 확인해 보기 바란다. 여기에서는 간단하게 소개만 하고 넘어가겠다.

1.sa_handler : sigaction의 signum에 해당되는 시그널이 전달되었을 때 실행될 시그널 핸들러

2.sa_mask : sa_handler에 등록된 시그널 핸들러가 실행되는 동안 블럭되어야 하는 시그널마스크


sigaction은 시그널을 객체단위로 제어할 수 있음을 알게 되었다. 이때 중요한 시그널 정책이, 시그널 핸들러가 수행되는 도중에 발생하는 시그널들을 set로 묶어서 관리하는 것이다. 이렇게 시그널을 set 으로 관리하기 위해서 다음의 함수들을 제공한다.

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigismember(sigset_t *set, int signum);
int sigsuspend(const sigset_t *mask);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigemptyset(sigset_t *set);
int sigdelset(sigset_t *set, int signo);
1.sigprocmask : 이것은 현재 set으로 등록된 시그널의 블럭 정책을 변경하기 위해서 사용한다. 블럭정책에는 다음의 3가지가 있다. 블럭set에 추가되어 있다면, 시그널은 바로 전달되지 않고 handler가 끝날때까지 블럭된다.

1.SIG_BLOCK : set에 설정된 시그널셋을 기존 블럭set에 추가한다.

2.SIG_UNBLOCK : set에 설정된 시그널 셋을 기존 블럭set에서 뺀다.

3.SIG_SETMASK : set의 시그널셋을 블럭set 정책으로 한다.

2.sigpending : 시그널이 블록된 상태에서 어떤 시그널이 발생해서 블록되었는지를 알 수 있다.

3.sigismember : signum이 시그널 set에 포함되어 있는지 확인한다. sigpending와 함께 사용하면, 어떤 시그널에 대해서 블록되었는지를 알고 이에 대한 처리를 할 수 있다.



#include
#include
#include

int main(int argc, char **argv)
{
sigset_t sigset;
sigset_t pendingset;
int i = 0;

// 모든 시그널에 대해서 BLOCK 한다.
sigfillset(&sigset);
sigprocmask(SIG_BLOCK, &sigset, NULL);

printf("My PID %d\n", getpid());
while(1)
{
printf("%d\n", i);
i++;
sleep(1);
// BLOCK된 시그널이 있다면
if (sigpending(&pendingset) == 0)
{
// 그리고 BLOCK된 시그널이 SIGUSR1 이라면
// 루프를 빠져나간다.
if (sigismember(&pendingset, SIGUSR1))
{
printf("BLOCKED Signal : SIGUSR1\n");
break;
}
}
}
return 0;
}
4.sigfillset : 모든 시그널셋의 bit flag를 on으로 한다. 예를들어 모든 시그널에 대해서 SIG_BLOCK를 적용하길 원한다면, 아래와 같이 하면 된다.



sigset_t sigset, oldset;
sigfillset(&sigset);
sigprocmask(SIG_BLOCK, &sigset, &oldset);
5.sigaddset : signum 번호를 가지는 시그널을 set에 추가한다.

6.sigemptyset : set을 모두 비운다.

7.sigdelset : signum 번호를 가지는 시그널을 set에서 지운다.


그럼 간단한 프로그램을 하나 만들어보도록 하겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include
#include
#include
#include

void sig_int(int signo);
void sig_usr(int signo);

int main()
{
int i = 0;
struct sigaction intsig, usrsig;

printf("PID : %d\n", getpid());
// SIGUSR2 시그널의 처리 ----------
usrsig.sa_handler = sig_usr; // 시그널 핸들러 등록
sigemptyset(&usrsig.sa_mask); // 시그널 마스크 초기화
usrsig.sa_flags = 0;
if (sigaction(SIGUSR2, &usrsig, 0) == -1)
{
printf ("signal(SIGUSR2) error");
return -1;
}
// ---------------------------------

// SIGINT (CTRL+C) 시그널의 처리 ---
intsig.sa_handler = sig_int;
sigemptyset(&intsig.sa_mask);
intsig.sa_flags = 0;
if (sigaction(SIGINT, &intsig, 0) == -1)
{
printf ("signal(SIGINT) error");
return -1;
}
// ---------------------------------

while(1)
{
printf("%d\n", i);
i++;
sleep(1);
}
}

void sig_int(int signo)
{
sigset_t sigset, oldset;
sigemptyset(&oldset);

// SIGUSR2와 SIGUSR1은 블럭된다.
// 이들 시그널은 핸들러가 종료되면 전달된다.
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR2);
sigaddset(&sigset, SIGUSR1);
if (sigprocmask(SIG_BLOCK, &sigset, &oldset) < 0)
{
printf("sigprocmask %d error \n", signo);
}

// SIGINT 를 UNBLOCK 한다.
// 핸들러가 수행중이더라도 즉시 전달된다.
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
if (sigprocmask(SIG_UNBLOCK, &sigset, &oldset) < 0)
{
printf("sigprocmask %d error \n", signo);
}

printf("sig_int\n");
sleep(5);
}

void sig_usr(int signo)
{
printf("sig_usr2\n");
}


■50~58 : SIGUSR1과 SIGUSR2 시그널을 블럭시켰다. 핸들러가 수행되는 5초동안 이들 시그널이 도착하면, 시그널은 BLOCK이 된다. 그러다가 시그널핸들러가 종료하면, 전달이 된다.

■60 ~68 : SIGINT를 UNBLOCK로 했다. 시그널핸들러가 수행되는 동안 동일한 시그널이 발생하게 되면, 시그널은 BLOCK이 된다. SIGINT에 대해서 UNBLOCK를 했으므로 SIGINT가 도착하게 되면, 곧바로 시그널이 전달되고 sig_int 시그널핸들러가 수행이 된다. 이 코드를 주석처리 한다음에 SIGINT를 여러번 발생시켜보기 바란다.


10 마치며 이상 간단하게 시그널에 대해서 알아보았다. 시그널의 활용과 관련된 내용은 thread를 설명하는 장을 비롯한 다른 몇몇 장에서, 다루게 될 것이다.

Linux Timer 예제

Linux Timer 예제

Linux signal()

Linux signal()

signal(2) - Linux man page
Name
signal - ANSI C signal handling
Synopsis
#include
typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

Description
The signal() system call installs a new signal handler for the signal with number signum. The signal handler is set to sighandler which may be a user specified function, or either SIG_IGN or SIG_DFL.
Upon arrival of a signal with number signum the following happens. If the corresponding handler is set to SIG_IGN, then the signal is ignored. If the handler is set to SIG_DFL, then the default action associated with the signal (see signal(7)) occurs. Finally, if the handler is set to a function sighandler then first either the handler is reset to SIG_DFL or an implementation-dependent blocking of the signal is performed and next sighandler is called with argument signum.

Using a signal handler function for a signal is called "catching the signal". The signals SIGKILL and SIGSTOP cannot be caught or ignored.

Return Value
The signal() function returns the previous value of the signal handler, or SIG_ERR on error.
Portability
The original Unix signal() would reset the handler to SIG_DFL, and System V (and the Linux kernel and libc4,5) does the same. On the other hand, BSD does not reset the handler, but blocks new instances of this signal from occurring during a call of the handler. The glibc2 library follows the BSD behaviour.
If one on a libc5 system includes instead of then signal() is redefined as __bsd_signal and signal has the BSD semantics. This is not recommended.

If one on a glibc2 system defines a feature test macro such as _XOPEN_SOURCE or uses a separate sysv_signal function, one obtains classical behaviour. This is not recommended.

Trying to change the semantics of this call using defines and includes is not a good idea. It is better to avoid signal() altogether, and use sigaction(2) instead.

Notes
The effects of this call in a multi-threaded process are unspecified.
The routine handler must be very careful, since processing elsewhere was interrupted at some arbitrary point. POSIX has the concept of "safe function". If a signal interrupts an unsafe function, and handler calls an unsafe function, then the behavior is undefined. Safe functions are listed explicitly in the various standards.

The POSIX.1-2003 list is

_Exit() _exit() abort() accept() access() aio_error() aio_return() aio_suspend() alarm() bind() cfgetispeed() cfgetospeed() cfsetispeed() cfsetospeed() chdir() chmod() chown() clock_gettime() close() connect() creat() dup() dup2() execle() execve() fchmod() fchown() fcntl() fdatasync() fork() fpathconf() fstat() fsync() ftruncate() getegid() geteuid() getgid() getgroups() getpeername() getpgrp() getpid() getppid() getsockname() getsockopt() getuid() kill() link() listen() lseek() lstat() mkdir() mkfifo() open() pathconf() pause() pipe() poll() posix_trace_event() pselect() raise() read() readlink() recv() recvfrom() recvmsg() rename() rmdir() select() sem_post() send() sendmsg() sendto() setgid() setpgid() setsid() setsockopt() setuid() shutdown() sigaction() sigaddset() sigdelset() sigemptyset() sigfillset() sigismember() signal() sigpause() sigpending() sigprocmask() sigqueue() sigset() sigsuspend() sleep() socket() socketpair() stat() symlink() sysconf() tcdrain() tcflow() tcflush() tcgetattr() tcgetpgrp() tcsendbreak() tcsetattr() tcsetpgrp() time() timer_getoverrun() timer_gettime() timer_settime() times() umask() uname() unlink() utime() wait() waitpid() write().

According to POSIX, the behaviour of a process is undefined after it ignores a SIGFPE, SIGILL, or SIGSEGV signal that was not generated by the kill(2) or the raise(3) functions. Integer division by zero has undefined result. On some architectures it will generate a SIGFPE signal. (Also dividing the most negative integer by -1 may generate SIGFPE.) Ignoring this signal might lead to an endless loop.

See sigaction(2) for details on what happens when SIGCHLD is set to SIG_IGN.

The use of sighandler_t is a GNU extension. Various versions of libc predefine this type; libc4 and libc5 define SignalHandler, glibc defines sig_t and, when _GNU_SOURCE is defined, also sighandler_t.

Conforming to
C89, POSIX.1-2001.
See Also
kill(1), alarm(2), kill(2), pause(2), sigaction(2), sigpending(2), sigprocmask(2), sigqueue(2), sigsuspend(2), killpg(3), raise(3), sigsetops(3), sigvec(3), feature_test_macros(7), signal(7)
Referenced By
bsd_signal(3), cdecl(1), fifo(4), fifo(7), getitimer(2), inndcomm(3), killpg(2), nnrpd(8), prctl(2), profil(3), ptrace(2), siginterrupt(3), sigreturn(2), sigset(3), sigvec(2), sigwaitinfo(2), sleep(3), ssignal(3), syscalls(2), system(3), sysv_signal(3), vga_blitwait(3), wait(2), wait4(2)

UTIME - Micro-Second Resolution Timers for Linux

UTIME - Micro-Second Resolution Timers for Linux

UTIME - Micro-Second Resolution Timers for Linux


--------------------------------------------------------------------------------


Latest Version
The latest version of UTIME is included in the KURT distribution.
--------------------------------------------------------------------------------

Old Releases
UTIME is now available for Linux 2.2.13 in two forms:
UTIME 2.2.13 w/ DSKI
UTIME 2.2.13
Gunzip the file and apply the patch from the top-level of a clean kernel with the following command (assuming the patch is in /tmp):
patch -p1 -s < /tmp/utime-2.2.13.patch
UTIME is also available as part of the KURT 2.0 distribution. Patches are avialable for Linux 2.2.5, 2.2.9, and 2.2.13.
--------------------------------------------------------------------------------


Copyright and Credits
Introduction
Usage
Implementation and Current Status
Files Modified
Known Bugs
Download Patches
Acknowledgments
Bug Reports and Suggestions

--------------------------------------------------------------------------------

0. Copyright and Credits
Copyright (C) 1997 by the University of Kansas Center for Research, Inc. This software was developed by the Information and Telecommunication Technology Center (ITTC) at the University of Kansas. Partial funding for this project was provided by Sprint. This software may be used and distributed according to the terms of the GNU Public License, incorporated herein by reference. Neither ITTC nor Sprint accept any liability whatsoever for this product.

This project was developed under the direction of Dr. Douglas Niehaus.

Authors:

Balaji S., Raghavan Menon
Furquan Ansari, Jason Keimig, Apurva Sheth
Please send bug-reports/suggestions/comments to utime@ittc.ku.edu



I. Introduction
Linux, as well as most other operating systems maintain a sense of time using a periodic interrupt from a timer chip, which is known as the "heartbeat" of the system. The heartbeat of the Linux kernel is 10 ms for the i386 and 1ms for the DEC Alpha. In our efforts to impart real-time characteristics to the Linux kernel, such a coarse-grained timing mechanism was found to be insufficient.

Since we have concentrated on implementing UTIME for the i386, the rest of this document uses terms that are specific to the i386.

One of the ways to increase the temporal granularity of Linux would be to program the timer chip of the PC to interrupt the kernel at higher frequencies. This is not an acceptable solution as the overhead increase due to this is tremendous. For example, if we program the timer chip to interrupt the CPU at 40 micro-sec, the interrupt processing cost is so high there is no time left for any other computation. So, basically, we need to program the timer chip to generate interrupts only when there is some scheduled work that needs to be accomplished. This is what we have achieved.

The following graph shows the time between when the time an event was scheduled to occur and the time it was actually executed. This was measured on a Pentium-200.



Figure: Distribution Plot for a Pentium (PS, GIF)




The graph below is on a Pentium-200 simulating a non-Pentium. (This shows the effectiveness of using the timer chip for keeping track of time).



Figure: Distribution Plot for a Non-Pentium (PS, GIF)



II. Usage
To use the UTIME system, patch the kernel with the supplied patch (Use patch -p1 -s < patch_file_name). Then configure the kernel and enable CONFIG_UTIME. This option is located in the "Kernel Hacking" submenu.

Once you configure the kernel and build it then when you boot up this kernel, it will try to calibrate itself. The calibrated speed can be observed with "cat /proc/cpuinfo". For example, for a 200 MHz Pentium "cat /proc/cpuinfo" should give you:

processor : 0
cpu : 586
model : Pentium 75+
vendor_id :
GenuineIntel stepping : 12
fdiv_bug : no
hlt_bug : no
sep_bug : no
fpu : yes
fpu_exception : yes
cpuid : yes
wp : yes
flags : fpu vme de pse tsc msr mce cx8
bogomips : 79.67
UTIME cps : 199435298 ticks per second
To add timers with microsecond resolution, set the timer->usec and the timer->expires field appropriately and use "add_timer()" to add the timer. You can test the UTIME subsystem by enabling CONFIG_UTIMER_TEST in the kernel configuration. Enabling this, would add random timers to the timeout queue and measure the time difference between when the timer was supposed to expire and when it actually did. A histogram of this is output to the console using syslog (The printk()'s use KERN_DEBUG as the level, so the messages should be in /var/log/debug, depending on your syslog configuration).

KU Real-Time (KURT) Linux is a firm real-time system we have developed that can take advantage of the increased precision of UTIME. (Note: The KURT patch already includes UTIME, so you only need to download one patch)



III. Implementation and Current Status
We have implemented the UTIME system for the i386. All of the architecture-dependent code is in the files:

include/asm-i386/mutime.h
include/asm-i386/mutime-M586.h
include/asm-i386/mutime-M386.h.
The latter two files contain inline routines specific to the Pentium and above processors and the 386/486 processors, respectively. To port the UTIME system to other architectures, you would have to provide the equivalent routines and macros listed in the above asm include files.

We have moved the timers part in sched.c into a new file kernel/timers.c This has been done to reduce the clutter in sched.c

To achieve microsecond resolution timing, we have added a field to the timer_list structure (see include/linux/timer.h). This field (usec) indicates the microsecond within the current "jiffy" that the timer is to timeout.

To maintain compatability with the rest of the kernel, we have introduced the notion of a software clock. This clock maintains the "jiffies" granularity and calls the "do_timer()" routine every jiffy, so that the rest of the system which depends on this would run properly.

In addition to the "jiffies" variable which tracks the number of 10ms ticks that have passed since startup, we have added a "jiffies_u" variable which tracks the micro-seconds within a 10ms tick. In the pentium and above processors we use the Pentium TSC to maintain this increased resolution. In non-Pentium machines we use the timer chip itself to get this increased resolution, though this is not very accurate.

We have introduced two modes of operation.

Periodic Mode:
In this mode the timer chip can be programmed to interrupt the CPU periodically using any resolution. This is generally useful when you want to increase the temporal resolution of the whole system by a certain amount.
Oneshot Mode:
In this mode the timer chip is programmed to interrupt the CPU only when there are events in the timer queue. If there are no events in the timer queue, the timer chip is automatically programmed to interrupt the cpu at 10ms boundaries.
You can switch the mode that the kernel is operating in by calling change_timer_mode() (see kernel/utime.c for usage).

Currently, the kernel is switched to oneshot mode as soon as the CPU counter is calibrated.



IV. Files Modified
New Files:
include/linux/mutime.h
include/asm/mutime.h
include/asm/mutime-M386.h
include/asm/mutime-M586.h
kernel/utime.c
kernel/hist.c
kernel/timers.c
Documentation/utime.txt
Modified Files:
include/linux/timer.h
include/linux/sched.h
kernel/sched.c
kernel/select.c
arch/i386/kernel/time.c
arch/i386/kernel/setup.c
init/main.c
include/linux/poll.h
kernel/Makefile



V. Known Bugs:
The machines lose time. This is only amounts to a few seconds in a day. This loss seems to happen only in the oneshot mode in the non-pentiums.
For the non-pentium machines, gettimeofday() can run backwards. In the latest release (v1.7) this does not happen too often. I have tried to fix this, but it does not seem to work correctly all the time . Anyone knowledgeable about this?? (NOTE: gettimeofday() works correctly for the Pentiums and above).
UTIME does not seem to work well with "X" running. With the new fast timers code in the 2.1.xx kernels, when you run X with UTIME the kernel freezes. Therefore, when CONFIG_UTIME is enabled, we now use the old timers code (ie a doubly linked list of timers). You can test out the kernel with the fast timers code by undefining DONT_USE_FAST_TIMERS in kernel/timers.c



VI. Download Patches
Current Patch
The current patch (version 1.17 - April 23, 1998) can be applied to a clean version of Linux 2.1.75. See the Change Log for changes since the last version.

A modified patch (version 1.17 - April 23, 1998) that will apply to a clean version of 2.0.33 is also available.



VII. Acknowledgements
We would like to thank Sprint Inc. for funding the research in this area. We are indebted to them. We would also like to thank Raghavan Menon who wrote the first draft of this system and Furquan Ansari, Apurva Seth and Jason Keimig who started this whole thing. I would like to thank Michael Barabanov for helping me with the non-Pentium code. Obviously without Dr. Niehaus' support and continued encouragement, we would never have had this in its present form. Presently the people working on this project are Balaji Srinivasan and Shyam Pather.



VIII. Bug-reports and Suggestions
Please send all bug-reports/questions/suggestions to utime@ittc.ku.edu. Comments are welcome on how we can improve this system.





UTIME @ ITTC
Last modified: Tue Sep 25 15:52:13 CDT 2001

Linux real-time clock

Linux real-time clock

rtc(4) - Linux man page
Name
rtc - real-time clock

Synopsis
#include

Description
This is the driver for the real-time clock (RTC).

Most computers have a built-in hardware clock, usually called the real-time clock. This clock is normally battery powered so that it keeps the time even while the computer is switched off. It represents the current time as year, month, day of month, hour, minute, and second.

The RTC is a chip that maintains the time and date and is able to generate interrupts at specified times. This chip typically used to be a Motorola MC146818, a Dallas DS12887, or similar, but today it is usually implemented in the mainboard's chipset.

The RTC should not be confused with the system time which is an independent, interrupt-driven software clock maintained by the kernel. The software clock is maintained by an interrupt routine that typically has a frequency of 100, 250, or 1000 Hz. The software clock counts seconds and microsecond since the POSIX Epoch, i.e., Jan 1, 1970, 0:00 UTC. This clock does not involve any special hardware.

The RTC can be read and set with hwclock(8).

The RTC is almost never used by the Linux kernel. Instead, the kernel uses the software clock time for time(2), gettimeofday(2), timestamps on files, etc. However, at boot time the kernel initializes its software clock by reading the RTC.

Besides counting the date and time, the RTC can also generate interrupts

*
on every clock update (i.e. once per second);

*

at periodic intervals with a frequency that can be set to any power-of-2 multiple in the range 2 Hz to 8192 Hz;

*

on reaching a previously specified alarm time.

Each of these interrupt sources can be enabled or disabled separately.
The /dev/rtc device can be opened only once simultaneously and it is read-only. On read(2) and select(2) the calling process is blocked until the next interrupt from the RTC is received. Following the interrupt, the process can read a long integer, of which the least significant byte contains the type of interrupt that occurred, while the remaining 3 bytes contain the number of interrupts since the last read(2).

The following ioctl(2) operations are provided:

RTC_RD_TIME
Returns the RTC time in the following structure:
struct rtc_time {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday; /* unused */
int tm_yday; /* unused */
int tm_isdst; /* unused */
};
The fields in this structure have the same meaning and ranges as for the tm structure described in gmtime(3). A pointer to this structure should be passed as the third ioctl() argument.
RTC_SET_TIME
Sets the RTC time to the time specified by the rtc_time structure pointed to by the third ioctl() argument. To set the RTC time the process must be privileged (i.e., have the CAP_SYS_TIME capability).
RTC_ALM_READ, RTC_ALM_SET
Read and set the alarm time. The third ioctl() argument is a pointer to an rtc_time structure. Only the tm_sec, tm_min, and tm_hour fields of this structure are used.
RTC_IRQP_READ, RTC_IRQP_SET
Read and set the frequency for periodic interrupts. The third ioctl() argument is a long * or a long, respectively. The value is the frequency in interrupts per second. The set of allowable frequencies is the multiples of two in the range 2 to 8192. Only a privileged process (i.e., one having the CAP_SYS_RESOURCE capability) can set frequencies above the value specified in /proc/sys/dev/rtc/max-user-freq. (This file contains the value 64 by default.)
RTC_AIE_ON, RTC_AIE_OFF
Enable or disable the alarm interrupt. The third ioctl() argument is ignored.
RTC_UIE_ON, RTC_UIE_OFF
Enable or disable the interrupt on every clock update. The third ioctl() argument is ignored.
RTC_PIE_ON, RTC_PIE_OFF
Enable or disable the periodic interrupt. The third ioctl() argument is ignored. Only a privileged process (i.e., one having the CAP_SYS_RESOURCE capability) can enable the periodic interrupt if the frequency is currently set above the value specified in /proc/sys/dev/rtc/max-user-freq.
RTC_EPOCH_READ, RTC_EPOCH_SET
The RTC encodes the year in an 8-bit register which is either interpreted as an 8-bit binary number or as a BCD number. In both cases, the number is interpreted relative to the RTC Epoch. The RTC Epoch is initialized to 1900 on most systems but on Alpha and Mips it might also be initialized to 1952, 1980, or 2000, depending on the value of RTC register for the year. These operations can be used to read or to set the RTC Epoch, respectively. To set the RTC Epoch the process must be privileged (i.e., have the CAP_SYS_TIME capability).
Files
/dev/rtc: the RTC special character device file.

/proc/driver/rtc: status of the RTC.

Notes
When the kernel's system time is synchronized with an external reference using adjtimex(2) it will update the RTC periodically every 11 minutes. To do so, the kernel has to briefly turn off periodic interrupts; this might affect programs using the RTC.

The RTC Epoch has nothing to do with the POSIX Epoch which is only used for the system clock.

If the year according to the RTC Epoch and the RTC's year register is less than 1970 it is assumed to be 100 years later, i.e. between 2000 and 2069.

See Also
hwclock(8), date(1), time(2), stime(2), gettimeofday(2), settimeofday(2), adjtimex(2), gmtime(3), time(7), /usr/src/linux/Documentation/rtc.txt

Message Queues

Message Queues

Subsections
Initialising the Message Queue
IPC Functions, Key Arguments, and Creation Flags:
Controlling message queues
Sending and Receiving Messages
POSIX Messages:
Example: Sending messages between two processes
message_send.c -- creating and sending to a simple message queue
message_rec.c -- receiving the above message
Some further example message queue programs
msgget.c: Simple Program to illustrate msget()
msgctl.cSample Program to Illustrate msgctl()
msgop.c: Sample Program to Illustrate msgsnd() and msgrcv()
Exercises

--------------------------------------------------------------------------------

IPC:Message Queues:
The basic idea of a message queue is a simple one.

Two (or more) processes can exchange information via access to a common system message queue. The sending process places via some (OS) message-passing module a message onto a queue which can be read by another process (Figure 24.1). Each message is given an identification or type so that processes can select the appropriate message. Process must share a common key in order to gain access to the queue in the first place (subject to other permissions -- see below).



Fig. 24.1 Basic Message Passing IPC messaging lets processes send and receive messages, and queue messages for processing in an arbitrary order. Unlike the file byte-stream data flow of pipes, each IPC message has an explicit length. Messages can be assigned a specific type. Because of this, a server process can direct message traffic between clients on its queue by using the client process PID as the message type. For single-message transactions, multiple server processes can work in parallel on transactions sent to a shared message queue.

Before a process can send or receive a message, the queue must be initialized (through the msgget function see below) Operations to send and receive messages are performed by the msgsnd() and msgrcv() functions, respectively.

When a message is sent, its text is copied to the message queue. The msgsnd() and msgrcv() functions can be performed as either blocking or non-blocking operations. Non-blocking operations allow for asynchronous message transfer -- the process is not suspended as a result of sending or receiving a message. In blocking or synchronous message passing the sending process cannot continue until the message has been transferred or has even been acknowledged by a receiver. IPC signal and other mechanisms can be employed to implement such transfer. A blocked message operation remains suspended until one of the following three conditions occurs:


The call succeeds.
The process receives a signal.
The queue is removed.
Initialising the Message Queue
The msgget() function initializes a new message queue:


int msgget(key_t key, int msgflg)

It can also return the message queue ID (msqid) of the queue corresponding to the key argument. The value passed as the msgflg argument must be an octal integer with settings for the queue's permissions and control flags.

The following code illustrates the msgget() function.

#include ;
#include ;

...


key_t key; /* key to be passed to msgget() */
int msgflg /* msgflg to be passed to msgget() */
int msqid; /* return value from msgget() */

...
key = ...
msgflg = ...

if ((msqid = msgget(key, msgflg)) == –1)
{
perror("msgget: msgget failed");
exit(1);
} else
(void) fprintf(stderr, “msgget succeeded");
...


IPC Functions, Key Arguments, and Creation Flags:
Processes requesting access to an IPC facility must be able to identify it. To do this, functions that initialize or provide access to an IPC facility use a key_t key argument. (key_t is essentially an int type defined in

The key is an arbitrary value or one that can be derived from a common seed at run time. One way is with ftok() , which converts a filename to a key value that is unique within the system. Functions that initialize or get access to messages (also semaphores or shared memory see later) return an ID number of type int. IPC functions that perform read, write, and control operations use this ID. If the key argument is specified as IPC_PRIVATE, the call initializes a new instance of an IPC facility that is private to the creating process. When the IPC_CREAT flag is supplied in the flags argument appropriate to the call, the function tries to create the facility if it does not exist already. When called with both the IPC_CREAT and IPC_EXCL flags, the function fails if the facility already exists. This can be useful when more than one process might attempt to initialize the facility. One such case might involve several server processes having access to the same facility. If they all attempt to create the facility with IPC_EXCL in effect, only the first attempt succeeds. If neither of these flags is given and the facility already exists, the functions to get access simply return the ID of the facility. If IPC_CREAT is omitted and the facility is not already initialized, the calls fail. These control flags are combined, using logical (bitwise) OR, with the octal permission modes to form the flags argument. For example, the statement below initializes a new message queue if the queue does not exist.


msqid = msgget(ftok("/tmp",
key), (IPC_CREAT | IPC_EXCL | 0400));

The first argument evaluates to a key based on the string ("/tmp"). The second argument evaluates to the combined permissions and control flags.


Controlling message queues
The msgctl() function alters the permissions and other characteristics of a message queue. The owner or creator of a queue can change its ownership or permissions using msgctl() Also, any process with permission to do so can use msgctl() for control operations.

The msgctl() function is prototypes as follows:


int msgctl(int msqid, int cmd, struct msqid_ds *buf )

The msqid argument must be the ID of an existing message queue. The cmd argument is one of:


IPC_STAT
-- Place information about the status of the queue in the data structure pointed to by buf. The process must have read permission for this call to succeed.
IPC_SET
-- Set the owner's user and group ID, the permissions, and the size (in number of bytes) of the message queue. A process must have the effective user ID of the owner, creator, or superuser for this call to succeed.
IPC_RMID
-- Remove the message queue specified by the msqid argument.
The following code illustrates the msgctl() function with all its various flags:


#include
#include
#include
...
if (msgctl(msqid, IPC_STAT, &buf) == -1) {
perror("msgctl: msgctl failed");
exit(1);
}
...
if (msgctl(msqid, IPC_SET, &buf) == -1) {
perror("msgctl: msgctl failed");
exit(1);
}
...


Sending and Receiving Messages
The msgsnd() and msgrcv() functions send and receive messages, respectively:


int msgsnd(int msqid, const void *msgp, size_t msgsz,
int msgflg);

int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);

The msqid argument must be the ID of an existing message queue. The msgp argument is a pointer to a structure that contains the type of the message and its text. The structure below is an example of what this user-defined buffer might look like:


struct mymsg {
long mtype; /* message type */
char mtext[MSGSZ]; /* message text of length MSGSZ */
}

The msgsz argument specifies the length of the message in bytes.

The structure member msgtype is the received message's type as specified by the sending process.

The argument msgflg specifies the action to be taken if one or more of the following are true:

The number of bytes already on the queue is equal to msg_qbytes.
The total number of messages on all queues system-wide is equal to the system-imposed limit.
These actions are as follows:
If (msgflg & IPC_NOWAIT) is non-zero, the message will not be sent and the calling process will return immediately.
If (msgflg & IPC_NOWAIT) is 0, the calling process will suspend execution until one of the following occurs:
The condition responsible for the suspension no longer exists, in which case the message is sent.
The message queue identifier msqid is removed from the system; when this occurs, errno is set equal to EIDRM and -1 is returned.
The calling process receives a signal that is to be caught; in this case the message is not sent and the calling process resumes execution.
Upon successful completion, the following actions are taken with respect to the data structure associated with msqid:

msg_qnum is incremented by 1.
msg_lspid is set equal to the process ID of the calling process.
msg_stime is set equal to the current time.
The following code illustrates msgsnd() and msgrcv():


#include
#include
#include

...

int msgflg; /* message flags for the operation */
struct msgbuf *msgp; /* pointer to the message buffer */
int msgsz; /* message size */
long msgtyp; /* desired message type */
int msqid /* message queue ID to be used */

...

msgp = (struct msgbuf *)malloc((unsigned)(sizeof(struct msgbuf)
- sizeof msgp->mtext + maxmsgsz));

if (msgp == NULL) {
(void) fprintf(stderr, "msgop: %s %d byte messages.\n",
"could not allocate message buffer for", maxmsgsz);
exit(1);

...

msgsz = ...
msgflg = ...

if (msgsnd(msqid, msgp, msgsz, msgflg) == -1)
perror("msgop: msgsnd failed");
...
msgsz = ...
msgtyp = first_on_queue;
msgflg = ...
if (rtrn = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) == -1)
perror("msgop: msgrcv failed");
...


POSIX Messages:
The POSIX message queue functions are:

mq_open() -- Connects to, and optionally creates, a named message queue.

mq_close() -- Ends the connection to an open message queue.

mq_unlink() -- Ends the connection to an open message queue and causes the queue to be removed when the last process closes it.

mq_send() -- Places a message in the queue.

mq_receive() -- Receives (removes) the oldest, highest priority message from the queue.

mq_notify() -- Notifies a process or thread that a message is available in the queue.

mq_setattr() -- Set or get message queue attributes.

The basic operation of these functions is as described above. For full function prototypes and further information see the UNIX man pages


Example: Sending messages between two processes
The following two programs should be compiled and run at the same time to illustrate basic principle of message passing:


message_send.c
-- Creates a message queue and sends one message to the queue.
message_rec.c
-- Reads the message from the queue.
message_send.c -- creating and sending to a simple message queue
The full code listing for message_send.c is as follows:


#include
#include
#include
#include
#include

#define MSGSZ 128


/*
* Declare the message structure.
*/

typedef struct msgbuf {
long mtype;
char mtext[MSGSZ];
} message_buf;

main()
{
int msqid;
int msgflg = IPC_CREAT | 0666;
key_t key;
message_buf sbuf;
size_t buf_length;

/*
* Get the message queue id for the
* "name" 1234, which was created by
* the server.
*/
key = 1234;

(void) fprintf(stderr, "\nmsgget: Calling msgget(%#lx,\
%#o)\n",
key, msgflg);

if ((msqid = msgget(key, msgflg )) < 0) {
perror("msgget");
exit(1);
}
else
(void) fprintf(stderr,"msgget: msgget succeeded: msqid = %d\n", msqid);


/*
* We'll send message type 1
*/

sbuf.mtype = 1;

(void) fprintf(stderr,"msgget: msgget succeeded: msqid = %d\n", msqid);

(void) strcpy(sbuf.mtext, "Did you get this?");

(void) fprintf(stderr,"msgget: msgget succeeded: msqid = %d\n", msqid);

buf_length = strlen(sbuf.mtext) + 1 ;



/*
* Send a message.
*/
if (msgsnd(msqid, &sbuf, buf_length, IPC_NOWAIT) < 0) {
printf ("%d, %d, %s, %d\n", msqid, sbuf.mtype, sbuf.mtext, buf_length);
perror("msgsnd");
exit(1);
}

else
printf("Message: \"%s\" Sent\n", sbuf.mtext);

exit(0);
}

The essential points to note here are:


The Message queue is created with a basic key and message flag msgflg = IPC_CREAT | 0666 -- create queue and make it read and appendable by all.
A message of type (sbuf.mtype) 1 is sent to the queue with the message ``Did you get this?''
message_rec.c -- receiving the above message
The full code listing for message_send.c's companion process, message_rec.c is as follows:


#include
#include
#include
#include

#define MSGSZ 128


/*
* Declare the message structure.
*/

typedef struct msgbuf {
long mtype;
char mtext[MSGSZ];
} message_buf;


main()
{
int msqid;
key_t key;
message_buf rbuf;

/*
* Get the message queue id for the
* "name" 1234, which was created by
* the server.
*/
key = 1234;

if ((msqid = msgget(key, 0666)) < 0) {
perror("msgget");
exit(1);
}


/*
* Receive an answer of message type 1.
*/
if (msgrcv(msqid, &rbuf, MSGSZ, 1, 0) < 0) {
perror("msgrcv");
exit(1);
}

/*
* Print the answer.
*/
printf("%s\n", rbuf.mtext);
exit(0);
}

The essential points to note here are:


The Message queue is opened with msgget (message flag 0666) and the same key as message_send.c.
A message of the same type 1 is received from the queue with the message ``Did you get this?'' stored in rbuf.mtext.

Some further example message queue programs
The following suite of programs can be used to investigate interactively a variety of massage passing ideas (see exercises below).

The message queue must be initialised with the msgget.c program. The effects of controlling the queue and sending and receiving messages can be investigated with msgctl.c and msgop.c respectively.


msgget.c: Simple Program to illustrate msget()

/*
* msgget.c: Illustrate the msgget() function.
* This is a simple exerciser of the msgget() function. It prompts
* for the arguments, makes the call, and reports the results.
*/

#include
#include
#include
#include

extern void exit();
extern void perror();

main()
{
key_t key; /* key to be passed to msgget() */
int msgflg, /* msgflg to be passed to msgget() */
msqid; /* return value from msgget() */

(void) fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
(void) fprintf(stderr, "IPC_PRIVATE == %#lx\n", IPC_PRIVATE);
(void) fprintf(stderr, "Enter key: ");
(void) scanf("%li", &key);
(void) fprintf(stderr, "\nExpected flags for msgflg argument
are:\n");
(void) fprintf(stderr, "\tIPC_EXCL =\t%#8.8o\n", IPC_EXCL);
(void) fprintf(stderr, "\tIPC_CREAT =\t%#8.8o\n", IPC_CREAT);
(void) fprintf(stderr, "\towner read =\t%#8.8o\n", 0400);
(void) fprintf(stderr, "\towner write =\t%#8.8o\n", 0200);
(void) fprintf(stderr, "\tgroup read =\t%#8.8o\n", 040);
(void) fprintf(stderr, "\tgroup write =\t%#8.8o\n", 020);
(void) fprintf(stderr, "\tother read =\t%#8.8o\n", 04);
(void) fprintf(stderr, "\tother write =\t%#8.8o\n", 02);
(void) fprintf(stderr, "Enter msgflg value: ");
(void) scanf("%i", &msgflg);

(void) fprintf(stderr, "\nmsgget: Calling msgget(%#lx,
%#o)\n",
key, msgflg);
if ((msqid = msgget(key, msgflg)) == -1)
{
perror("msgget: msgget failed");
exit(1);
} else {
(void) fprintf(stderr,
"msgget: msgget succeeded: msqid = %d\n", msqid);
exit(0);
}
}


msgctl.cSample Program to Illustrate msgctl()

/*
* msgctl.c: Illustrate the msgctl() function.
*
* This is a simple exerciser of the msgctl() function. It allows
* you to perform one control operation on one message queue. It
* gives up immediately if any control operation fails, so be
careful
* not to set permissions to preclude read permission; you won't
be
* able to reset the permissions with this code if you do.
*/
#include
#include
#include
#include
#include

static void do_msgctl();
extern void exit();
extern void perror();
static char warning_message[] = "If you remove read permission
for \
yourself, this program will fail frequently!";

main()
{
struct msqid_ds buf; /* queue descriptor buffer for IPC_STAT
and IP_SET commands */
int cmd, /* command to be given to msgctl() */
msqid; /* queue ID to be given to msgctl() */

(void fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");

/* Get the msqid and cmd arguments for the msgctl() call. */
(void) fprintf(stderr,
"Please enter arguments for msgctls() as requested.");
(void) fprintf(stderr, "\nEnter the msqid: ");
(void) scanf("%i", &msqid);
(void) fprintf(stderr, "\tIPC_RMID = %d\n", IPC_RMID);
(void) fprintf(stderr, "\tIPC_SET = %d\n", IPC_SET);
(void) fprintf(stderr, "\tIPC_STAT = %d\n", IPC_STAT);
(void) fprintf(stderr, "\nEnter the value for the command: ");
(void) scanf("%i", &cmd);

switch (cmd) {
case IPC_SET:
/* Modify settings in the message queue control structure.
*/
(void) fprintf(stderr, "Before IPC_SET, get current
values:");
/* fall through to IPC_STAT processing */
case IPC_STAT:
/* Get a copy of the current message queue control
* structure and show it to the user. */
do_msgctl(msqid, IPC_STAT, &buf);
(void) fprintf(stderr, ]
"msg_perm.uid = %d\n", buf.msg_perm.uid);
(void) fprintf(stderr,
"msg_perm.gid = %d\n", buf.msg_perm.gid);
(void) fprintf(stderr,
"msg_perm.cuid = %d\n", buf.msg_perm.cuid);
(void) fprintf(stderr,
"msg_perm.cgid = %d\n", buf.msg_perm.cgid);
(void) fprintf(stderr, "msg_perm.mode = %#o, ",
buf.msg_perm.mode);
(void) fprintf(stderr, "access permissions = %#o\n",
buf.msg_perm.mode & 0777);
(void) fprintf(stderr, "msg_cbytes = %d\n",
buf.msg_cbytes);
(void) fprintf(stderr, "msg_qbytes = %d\n",
buf.msg_qbytes);
(void) fprintf(stderr, "msg_qnum = %d\n", buf.msg_qnum);
(void) fprintf(stderr, "msg_lspid = %d\n",
buf.msg_lspid);
(void) fprintf(stderr, "msg_lrpid = %d\n",
buf.msg_lrpid);
(void) fprintf(stderr, "msg_stime = %s", buf.msg_stime ?
ctime(&buf.msg_stime) : "Not Set\n");
(void) fprintf(stderr, "msg_rtime = %s", buf.msg_rtime ?
ctime(&buf.msg_rtime) : "Not Set\n");
(void) fprintf(stderr, "msg_ctime = %s",
ctime(&buf.msg_ctime));
if (cmd == IPC_STAT)
break;
/* Now continue with IPC_SET. */
(void) fprintf(stderr, "Enter msg_perm.uid: ");
(void) scanf ("%hi", &buf.msg_perm.uid);
(void) fprintf(stderr, "Enter msg_perm.gid: ");
(void) scanf("%hi", &buf.msg_perm.gid);
(void) fprintf(stderr, "%s\n", warning_message);
(void) fprintf(stderr, "Enter msg_perm.mode: ");
(void) scanf("%hi", &buf.msg_perm.mode);
(void) fprintf(stderr, "Enter msg_qbytes: ");
(void) scanf("%hi", &buf.msg_qbytes);
do_msgctl(msqid, IPC_SET, &buf);
break;
case IPC_RMID:
default:
/* Remove the message queue or try an unknown command. */
do_msgctl(msqid, cmd, (struct msqid_ds *)NULL);
break;
}
exit(0);
}

/*
* Print indication of arguments being passed to msgctl(), call
* msgctl(), and report the results. If msgctl() fails, do not
* return; this example doesn't deal with errors, it just reports
* them.
*/
static void
do_msgctl(msqid, cmd, buf)
struct msqid_ds *buf; /* pointer to queue descriptor buffer */
int cmd, /* command code */
msqid; /* queue ID */
{
register int rtrn; /* hold area for return value from msgctl()
*/

(void) fprintf(stderr, "\nmsgctl: Calling msgctl(%d, %d,
%s)\n",
msqid, cmd, buf ? "&buf" : "(struct msqid_ds *)NULL");
rtrn = msgctl(msqid, cmd, buf);
if (rtrn == -1) {
perror("msgctl: msgctl failed");
exit(1);
} else {
(void) fprintf(stderr, "msgctl: msgctl returned %d\n",
rtrn);
}
}


msgop.c: Sample Program to Illustrate msgsnd() and msgrcv()

/*
* msgop.c: Illustrate the msgsnd() and msgrcv() functions.
*
* This is a simple exerciser of the message send and receive
* routines. It allows the user to attempt to send and receive as
many
* messages as wanted to or from one message queue.
*/

#include
#include
#include
#include

static int ask();
extern void exit();
extern char *malloc();
extern void perror();

char first_on_queue[] = "-> first message on queue",
full_buf[] = "Message buffer overflow. Extra message text\
discarded.";

main()
{
register int c; /* message text input */
int choice; /* user's selected operation code */
register int i; /* loop control for mtext */
int msgflg; /* message flags for the operation */
struct msgbuf *msgp; /* pointer to the message buffer */
int msgsz; /* message size */
long msgtyp; /* desired message type */
int msqid, /* message queue ID to be used */
maxmsgsz, /* size of allocated message buffer */
rtrn; /* return value from msgrcv or msgsnd */
(void) fprintf(stderr,
"All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr,
"\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
/* Get the message queue ID and set up the message buffer. */
(void) fprintf(stderr, "Enter msqid: ");
(void) scanf("%i", &msqid);
/*
* Note that includes a definition of struct
msgbuf
* with the mtext field defined as:
* char mtext[1];
* therefore, this definition is only a template, not a
structure
* definition that you can use directly, unless you want only
to
* send and receive messages of 0 or 1 byte. To handle this,
* malloc an area big enough to contain the template - the size
* of the mtext template field + the size of the mtext field
* wanted. Then you can use the pointer returned by malloc as a
* struct msgbuf with an mtext field of the size you want. Note
* also that sizeof msgp->mtext is valid even though msgp
isn't
* pointing to anything yet. Sizeof doesn't dereference msgp,
but
* uses its type to figure out what you are asking about.
*/
(void) fprintf(stderr,
"Enter the message buffer size you want:");
(void) scanf("%i", &maxmsgsz);
if (maxmsgsz < 0) {
(void) fprintf(stderr, "msgop: %s\n",
"The message buffer size must be >= 0.");
exit(1);
}
msgp = (struct msgbuf *)malloc((unsigned)(sizeof(struct
msgbuf)
- sizeof msgp->mtext + maxmsgsz));
if (msgp == NULL) {
(void) fprintf(stderr, "msgop: %s %d byte messages.\n",
"could not allocate message buffer for", maxmsgsz);
exit(1);
}
/* Loop through message operations until the user is ready to
quit. */
while (choice = ask()) {
switch (choice) {
case 1: /* msgsnd() requested: Get the arguments, make the
call, and report the results. */
(void) fprintf(stderr, "Valid msgsnd message %s\n",
"types are positive integers.");
(void) fprintf(stderr, "Enter msgp->mtype: ");
(void) scanf("%li", &msgp->mtype);
if (maxmsgsz) {
/* Since you've been using scanf, you need the loop
below to throw away the rest of the input on the
line after the entered mtype before you start
reading the mtext. */
while ((c = getchar()) != '\n' && c != EOF);
(void) fprintf(stderr, "Enter a %s:\n",
"one line message");
for (i = 0; ((c = getchar()) != '\n'); i++) {
if (i >= maxmsgsz) {
(void) fprintf(stderr, "\n%s\n", full_buf);
while ((c = getchar()) != '\n');
break;
}
msgp->mtext[i] = c;
}
msgsz = i;
} else
msgsz = 0;
(void) fprintf(stderr,"\nMeaningful msgsnd flag is:\n");
(void) fprintf(stderr, "\tIPC_NOWAIT =\t%#8.8o\n",
IPC_NOWAIT);
(void) fprintf(stderr, "Enter msgflg: ");
(void) scanf("%i", &msgflg);
(void) fprintf(stderr, "%s(%d, msgp, %d, %#o)\n",
"msgop: Calling msgsnd", msqid, msgsz, msgflg);
(void) fprintf(stderr, "msgp->mtype = %ld\n",
msgp->mtype);
(void) fprintf(stderr, "msgp->mtext = \"");
for (i = 0; i < msgsz; i++)
(void) fputc(msgp->mtext[i], stderr);
(void) fprintf(stderr, "\"\n");
rtrn = msgsnd(msqid, msgp, msgsz, msgflg);
if (rtrn == -1)
perror("msgop: msgsnd failed");
else
(void) fprintf(stderr,
"msgop: msgsnd returned %d\n", rtrn);
break;
case 2: /* msgrcv() requested: Get the arguments, make the
call, and report the results. */
for (msgsz = -1; msgsz < 0 || msgsz > maxmsgsz;
(void) scanf("%i", &msgsz))
(void) fprintf(stderr, "%s (0 <= msgsz <= %d): ",
"Enter msgsz", maxmsgsz);
(void) fprintf(stderr, "msgtyp meanings:\n");
(void) fprintf(stderr, "\t 0 %s\n", first_on_queue);
(void) fprintf(stderr, "\t>0 %s of given type\n",
first_on_queue);
(void) fprintf(stderr, "\t<0 %s with type <= |msgtyp|\n",
first_on_queue);
(void) fprintf(stderr, "Enter msgtyp: ");
(void) scanf("%li", &msgtyp);
(void) fprintf(stderr,
"Meaningful msgrcv flags are:\n");
(void) fprintf(stderr, "\tMSG_NOERROR =\t%#8.8o\n",
MSG_NOERROR);
(void) fprintf(stderr, "\tIPC_NOWAIT =\t%#8.8o\n",
IPC_NOWAIT);
(void) fprintf(stderr, "Enter msgflg: ");
(void) scanf("%i", &msgflg);
(void) fprintf(stderr, "%s(%d, msgp, %d, %ld, %#o);\n",
"msgop: Calling msgrcv", msqid, msgsz,
msgtyp, msgflg);
rtrn = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg);
if (rtrn == -1)
perror("msgop: msgrcv failed");
else {
(void) fprintf(stderr, "msgop: %s %d\n",
"msgrcv returned", rtrn);
(void) fprintf(stderr, "msgp->mtype = %ld\n",
msgp->mtype);
(void) fprintf(stderr, "msgp->mtext is: \"");
for (i = 0; i < rtrn; i++)
(void) fputc(msgp->mtext[i], stderr);
(void) fprintf(stderr, "\"\n");
}
break;
default:
(void) fprintf(stderr, "msgop: operation unknown\n");
break;
}
}
exit(0);
}

/*
* Ask the user what to do next. Return the user's choice code.
* Don't return until the user selects a valid choice.
*/
static
ask()
{
int response; /* User's response. */

do {
(void) fprintf(stderr, "Your options are:\n");
(void) fprintf(stderr, "\tExit =\t0 or Control-D\n");
(void) fprintf(stderr, "\tmsgsnd =\t1\n");
(void) fprintf(stderr, "\tmsgrcv =\t2\n");
(void) fprintf(stderr, "Enter your choice: ");

/* Preset response so "^D" will be interpreted as exit. */
response = 0;
(void) scanf("%i", &response);
} while (response < 0 || response > 2);

return(response);
}


Exercises
Exercise 12755

Write a 2 programs that will both send and messages and construct the following dialog between them


(Process 1) Sends the message "Are you hearing me?"
(Process 2) Receives the message and replies "Loud and Clear".
(Process 1) Receives the reply and then says "I can hear you too".


Exercise 12756

Compile the programs msgget.c, msgctl.c and msgop.c and then

investigate and understand fully the operations of the flags (access, creation etc. permissions) you can set interactively in the programs.
Use the programs to:
Send and receive messages of two different message types.
Place several messages on the queue and inquire about the state of the queue with msgctl.c. Add/delete a few messages (using msgop.c and perform the inquiry once more.
Use msgctl.c to alter a message on the queue.
Use msgctl.c to delete a message from the queue.


Exercise 12757

Write a server program and two client programs so that the server can communicate privately to each client individually via a single message queue.


Exercise 12758

Implement a blocked or synchronous method of message passing using signal interrupts.





--------------------------------------------------------------------------------

Dave Marshall
1/5/1999

쫄지마, 형사절차!

쫄지마, 형사절차!

민변(민주사회를 위한 변호사 모임)에서 시민들 자신이 인권을 직접 챙기고 보호할 수 있는 쉬운 안내 책자를 만들었다. 법을 가능하면 쉬운 용어와 말로 썼다. 또한 거창한 이야기가 아니라 일상에서 흔히 일어나는 사례를 중심으로 생동감 있게 설명했다. 그리고 기술 진보에 따른 새로운 유형의 인권침해 사례와 대응방법도 함께 살펴본다.

2009년 12월 10일 목요일

2010 업계지도 (리더스하우스)

2010 업계지도 (리더스하우스)

[이데일리 김수헌기자] 2008년 5월, 이데일리는 `우리나라에서 처음으로` 제조 IT 유통 부동산 등 산업계와 증권 은행 카드 자산운용 등 금융업계의 지형도를 그래픽으로 정리한 '2008 업계지도'를 내놓았습니다.

그리고 그해 말, 2008년판을 완전히 환골탈태시킨 '2009 업계지도'를 세상에 선보였습니다. 반응은 뜨거웠습니다. "한국에도 이런 책이 나오는구나. 도대체 어떻게 이런 책을 만들었나"라며 놀라움을 전해오는 분들이 많았습니다.

이런 호평속에 2008년판과 2009년판이 베스트셀러 대열에 진입했습니다. 하지만 이데일리는 만족하지 않았습니다.

적은 비용으로 적당한 수준의 책을 만들어서 돈을 만져보겠다는 얄팍한 상술이 머리를 지배하는 순간, 업계지도는 끝난다고 생각했습니다.

경제전문매체 이데일리의 자존심과 자부심, 기자들의 역량이 녹아있는 대한민국 최고의 업계지도를 만들어야 한다는 각오을 다졌습니다.

이데일리가 드디어 '2010 업계지도'(리더스하우스刊)를 내놓았습니다. 출간되자마자 글자 그대로 선풍적인 인기몰이를 하고 있습니다. 상술을 앞세우지 않고, 노력한만큼 인정받겠다는 각오로 만들었다는 것을 독자들이 평가해 준 것이라고 생각합니다.

2010년판은 산업 금융 증권 부동산 유통 IT 제약부문 전문기자 21명이 3개월여동안 발로 뛰며 만들었습니다. 2009년판을 갖고 계신다면, 2010년판과 각각의 업종들을 펼쳐가며 세심하게 비교해 보십시오. 이데일리 기자들의 땀이 어떻게 책에서 구현됐는지 아실 겁니다.

2010년판은 '세상에서 가장 간편하고 알기 쉬우면서도, 풍부한 콘텐트를 담은 업계 조감도'를 그려내겠다는 각오로 탄생시킨 역작(力作)입니다. 2009년판을 다시 두단계 이상 진화시켰다고 자부합니다. 증권가 베스트 애널리스트들도 예리한 분석과 전망을 2010년판에 실어줘 책의 가치를 더했습니다.

업계지도는 국내 70개 업종과 주요 기업에 대한 모든 정보를 글이나 복잡한 도표가 아닌 깔끔한 그래픽과 그림을 통해, 한 눈에 파악할 수 있게끔 구성돼 있습니다.

기업정보를 그래픽화하기 위해서는 힘든 작업을 거쳐야 합니다. 우선 그 업계를 가장 잘 이해하고 있는 기자가 최근의 각종 공시와 보고서를 샅샅이 뒤져야 합니다. 때로는 업체에 많은 자료를 요청해야 하고, 직접 팩트확인을 위한 취재과정도 거쳐야 합니다.

이렇게 해서 취합한 모든 자료를 놓고, 그림과 그래픽으로 가장 쉽게 독자들에게 전달할 방법을 구상해야 합니다. 모든 과정 하나하나가 간단치 않습니다.

2010년판에는 개별기업의 업종 내 순위와 전사(全社) 경영실적(최근 2년간 매출 영업이익 순이익), 사업구조와 사업부문별 실적, 재무 건전성, 성장성, 안정성 등을 파악할 수 있는 각종 지표들이 기본 그래픽으로 깔끔하게 정리돼 있습니다.

여기에 출자관계를 포함해 그 기업이 속한 그룹의 전체 지배구조(출자 지형도), 그 기업과 관련한 구조조정 및 성장 히스토리, 최근 인수합병(M&A) 관계, 신규사업 및 최근 부각된 이슈 등이 바로 파악되게끔 그림과 그래픽으로 짜여져 있는 게 가장 큰 특징입니다.

구체적으로 책의 내용으로 잠시 엿보면 이데일리 업계지도의 진가를 알 수 있습니다.

예를 들어 아래 그림 '석유화학업종편'(10쪽 짜리)을 보십시오.




석유화학업종 그래픽은 ▲LG 롯데계열 ▲SK계열 ▲한화 대림계열 ▲금호아시아나계열 ▲삼성계열, 그리고 ▲독립계열(GS칼텍스 OCI KCC 등)로 크게 구분돼 있습니다.

이렇게 구분한 이유는 최근 10년간 석유화학업종에서 복잡한 이합집산 및 구조조정 과정이 있었으며, 이러한 과정을 독자들이 좀 더 쉽게 이해할 수 있게 돕기 위해서입니다.

LG 롯데계열을 보면 각 그룹 내 주요 화학기업들의 2009년과 2008년 경영실적을 그래픽화했고, 핵심 사업부분별 실적과 사업별 시장점유율, 제품별 매출비중, 중장기 영업이익추세, 생산능력 등을 그림으로 잘 정리해 놓았습니다.

그리고 무엇보다도 기업들간 인수합병 스토리가 그림으로 잘 정리돼 있습니다. 옛 현대석유화학이 3개사로 쪼개졌고 이 가운데 2개 회사가 LG화학과 호남석유화학으로 흡수합병됐습니다.

LG화학은 LG생활건강을 분할독립시켰고 LG석유화학을 합병했으며, 최근에는 산업재 사업부를 따로 떼내 LG하우시스(108670)(119,500원 2,500 +2.14%)라는 회사를 탄생시켰습니다.

호남석유화학의 히스토리도 잘 나와 있습니다. 롯데대산유화를 흡수했고 최근 KP케미칼과의 합병을 추진했다가 무산됐다는 사실까지 그림으로 잘 정리돼 있습니다.




SK그룹 계열로 넘어가 보면, SK에너지(096770)(113,000원 4,000 +3.67%) SKC SK케미칼(006120)(69,600원 200 +0.29%) SK유화 등의 최근 실적이 어떠했는지, 지분관계는 어떻게 얽혀있는지 등이 잘 나와있습니다. 실적은 어떤지, 어떤 사업구조를 갖고 있는지, 사업부문들의 경쟁력은 어느 정도인지 등이 바로 파악됩니다.




그림에서 보면 한화(000880)(46,150원 1,200 +2.67%)와 대림은 여천NCC를 50대50으로 공동 합작경영하고 있습니다. 한화그룹의 전체 출자구조도가 깔끔하게 쏙 들어오며, 한화그룹 화학사업의 수직계열화가 어떤 식으로 짜여져 있는지도 금방 알 수 있습니다. 금호아시아나 계열이나 삼성그룹 계열도 마찬가지입니다.









이런 내용들은 그 어느 곳에도 한번에 취합정리돼 있는 곳이 없습니다. 공시와 사업보고서, 증권 리포트, 홈페이지를 샅샅이 뒤지고 기업에 자료를 요청하고, 또 모인 자료를 종합정리 검증하는 절차를 거쳐야 해낼 수 있는 그래픽들입니다.

그래서 이데일리 기자들이 발로 뛰며 대한민국 최고의 업계지도를 만들어냈다고 자부할 수 있는 것입니다.

아래 '통신서비스편', '화학섬유편', '해운편'을 한번 보십시오.














2010년판은 이처럼 2009년판에 비해 모든 면에서 크게 진화했습니다. 구체적으로 보면 우선 내용이 크게 풍부해졌습니다. 70페이지 이상 확 늘었습니다.

이데일리 업계지도는 그래픽 뿐 아니라 취재기자들이 업계의 현재 이슈와 미래전망 등을 정리한 글도 각 업종마다 2쪽에 걸쳐 실어왔습니다.

2010년판에는 여기에다 더해 각 업종별 국내 최고의 베스트 애널리스트들이 전망한 내년도 이슈와 전망을 담았습니다.(애널리스트의 눈)

아울러 베스트 애널리스트들이 전망한 '2010년 업계 기상예보(UP-FLAT-DOWN)'를 10쪽에 걸쳐 핵심정리했습니다.

'신성장산업편'을 추가한 점도 크게 눈에 띕니다. 태양광 풍력 스마트그리드 온실가스 그린카 2차전지 OLED LED 내비게이션 등의 산업을 영위하는 기업들의 경영상황 뿐 아니라 신성장 산업이라는게 도대체 어떤 사업이며 전문가들은 어떻게 전망하는지도 간결하게 잘 정리해놓았습니다.

완벽한 책이란 있을 수 없겠지만, 이보다 더 나은 책을 만들 수는 없다는 생각으로 이데일리 기자 21명이 열심히 만들었습니다. '2010 업계지도'입니다.

<저자 이데일리 소개>
이데일리(edaily)는 국내 최대 온라인경제신문과 케이블 경제방송 '이데일리TV', 금융정보 경제뉴스 단말기 '마켓포인트'를 3대 주축으로 한 멀티미디어 종합경제뉴스매체다.

국내외 경제관련 뉴스를 '가장 정확하고 가장 빠르게, 그리고 깊이있게' 전달한다는 목표 아래 경제지 종합지 전문지 방송사 통신사 출신의 역량있는 기자들이 모여 2000년 3월 출범했다.

100여명의 기자로 구성된 국내 최대 온라인편집국에서 증권 금융 산업 정책 부동산 국제경제는 물론 생활경제에 이르기까지 모든 경제뉴스를 생산해 100여개 금융회사와 언론사, 세계적 통신사, 포털사이트 등에 공급하는 경제지식탱크다.

총 300여명에 이르는 전사 인력이 대부분 뉴스컨텐츠 생산 관련 업무에 종사하며 종합경제뉴스매체로서 강력한 경쟁력을 확보하고 있고, 미국 뉴욕과 중국 상하이 등 세계경제 중심지에 특파원을 파견해 글로벌네트워크를 갖춰가고 있다.

하루 14시간 생방송으로 경제뉴스를 전달하는 케이블방송 '이데일리TV', 금융정보와 경제뉴스를 담은 종합경제단말기 '마켓포인트', 온라인 증권투자 전문가방송사이트 '이데일리ON(www.edailyON.co.kr)' 연예 스포츠 전문포털 SPN(www.edailySPN.co.kr) 등을 운영하며 이데일리는 종합미디어그룹으로 발돋움하고 있다.

온라인과 케이블방송, 단말기터미널 등 다양한 뉴스유통채널을 갖추고 언론매체로서 역량을 강화하고 있는 이데일리는 온라인매체로는 처음으로 기자협회와 한국언론재단이 수여하는 '이달의 기자상'을 받았고, 지금까지 4회에 걸쳐 수상하는 등 온라인매체 최다수상 기록도 가지고 있다. 이밖에 세계적 금융회사인 씨티그룹이 수여하는 '대한민국 언론인상'을 3년 연속 수상하는 등 다양한 수상기록을 보유하고 있다.

실내식물기르기




영하의 찬바람에 창과 문을 닫고 한낮에도 난방을 켜야 하는 겨울이다. 겨울철 실내공기는 난방으로 건조해지는 데다 환기를 자주 못해 탁해진다. 이 때문에 호흡기가 약한 아이들은 연방 기침을 하고 코를 훌쩍거린다. 바로 이때 실내 식물 기르기가 도움이 된다. 식물은 ‘천연 공기정화 가습기’다. 습도를 유지시켜줄 뿐만 아니라 포름알데히드·벤젠 등 새집증후군과 아토피를 유발하는 휘발성 오염물질을 제거한다. 또 이산화탄소를 산소로 바꿔 맑은 공기를 제공한다. 식물은 암모니아를 제거하거나 음이온을 방출하는 등 종류에 따라 각기 다른 특성을 가지고 있다. 이 특성에 맞춰 거실·침실·주방 등 환경에 따라 집안에 배치하면 공기정화 효과를 볼 수 있다.

민동기 기자

개인형 2bay miniNAS

개인형 2bay miniNAS

일반 PC대비 4%의 저전력으로 운용이 가능한
2bay miniNAS

N0204는 개인형 2bay miniNAS 입니다.
기존 Thecus 제품의 모든 기능을 가지고 있을 뿐더러 저전력, 저소음으로 집에서 사용하기 더욱 좋아졌고 강력한 네트워크 저장장치 기능으로 개인이 사용하기에 매우 적합한 제품입니다.

"nm" command

Library Info:

The command "nm" lists symbols contained in the object file or shared library.

Use the command nm -D libctest.so.1.0
(or nm --dynamic libctest.so.1.0)

0000000000100988 A __bss_start
000000000000068c T ctest1
00000000000006a0 T ctest2
w __cxa_finalize
00000000001007b0 A _DYNAMIC
0000000000100988 A _edata
0000000000100990 A _end
00000000000006f8 T _fini
0000000000100958 A _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000000005b0 T _init
w _Jv_RegisterClasses

Man page for nm
Symbol Type Description
A The symbol's value is absolute, and will not be changed by further linking.
B Un-initialized data section
D Initialized data section
T Normal code section
U Undefined symbol used but not defined. Dependency on another library.
W Doubly defined symbol. If found, allow definition in another library to resolve dependency.

openssl PASS PHRASE ARGUMENTS

openssl PASS PHRASE ARGUMENTS

# provide password on command line
openssl enc -aes-256-cbc -salt -in file.txt \
-out file.enc -pass pass:mySillyPassword

# provide password in a file
openssl enc -aes-256-cbc -salt -in file.txt \
-out file.enc -pass file:/path/to/secret/password.txt

OpenSSL Command-Line HOWTO

OpenSSL Command-Line HOWTO


madboa.com Home

Geek stuff

Praise songs

Paul's page

Book notes

This site
OpenSSL Command-Line HOWTO
Paul Heinlein

Initial publication: June 13, 2004
Most recent revision: February 11, 2009
The openssl application that ships with the OpenSSL libraries can perform a wide range of crypto operations. This HOWTO provides some cookbook-style recipes for using it.


--------------------------------------------------------------------------------

Table of Contents

Introduction
How do I find out what OpenSSL version I’m running?
How do I get a list of the available commands?
How do I get a list of available ciphers?
Benchmarking
How do I benchmark my system’s performance?
How do I benchmark remote connections?
Certificates
How do I generate a self-signed certificate?
How do I generate a certificate request for VeriSign?
How do I test a new certificate?
How do I retrieve a remote certificate?
How do I extract information from a certificate?
How do I export or import a PKCS#12 certificate?
Certificate Verification
How do I verify a certificate?
What certificate authorities does OpenSSL recognize?
How do I get OpenSSL to recognize/verify a certificate?
Command-line clients and servers
How do I connect to a secure SMTP server?
How do I connect to a secure [whatever] server?
How do I set up an SSL server from the command line?
Digests
How do I create an MD5 or SHA1 digest of a file?
How do I sign a digest?
How do I verify a signed digest?
How do I create an Apache digest password entry?
What other kinds of digests are available?
Encryption/Decryption
How do I base64-encode something?
How do I simply encrypt a file?
Errors
How do I interpret SSL error messages?
Keys
How do I generate an RSA key?
How do I generate a public RSA key?
How do I generate a DSA key?
How do I create an elliptic curve key?
How do I remove a passphrase from a key?
Password hashes
How do I generate a crypt-style password hash?
How do I generate a shadow-style password hash?
Prime numbers
How do I test whether a number is prime?
How do I generate a set of prime numbers?
Random data
How do I generate random data?
S/MIME
How do I verify a signed S/MIME message?
How do I encrypt a S/MIME message?
How do I sign a S/MIME message?
For further reading
Comments welcome
Introduction
The openssl command-line binary that ships with the OpenSSL libraries can perform a wide range of cryptographic operations. It can come in handy in scripts or for accomplishing one-time command-line tasks.

Documentation for using the openssl application is somewhat scattered, however, so this article aims to provide some practical examples of its use. I assume that you’ve already got a functional OpenSSL installation and that the openssl binary is in your shell’s PATH.

Just to be clear, this article is strictly practical; it does not concern cryptographic theory and concepts. If you don’t know what an MD5 sum is, this article won’t enlighten you one bit—but if all you need to know is how to use openssl to generate a file sum, you’re in luck.

The nature of this article is that I’ll be adding new examples incrementally. Check back at a later date if I haven’t gotten to the information you need.

How do I find out what OpenSSL version I’m running?
Use the version option.

$ openssl version
OpenSSL 0.9.8b 04 May 2006
You can get much more information with the version -a option.

$ openssl version -a
OpenSSL 0.9.8b 04 May 2006
built on: Fri Sep 29 18:45:58 UTC 2006
platform: debian-i386-i686/cmov
options: bn(64,32) md2(int) rc4(idx,int) des(ptr,risc1,16,long) blowfish(idx)
compiler: gcc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT
-DDSO_DLFCN -DHAVE_DLFCN_H -DL_ENDIAN -DTERMIO -O3 -march=i686
-Wa,--noexecstack -g -Wall -DOPENSSL_BN_ASM_PART_WORDS -DOPENSSL_IA32_SSE2
-DSHA1_ASM -DMD5_ASM -DRMD160_ASM -DAES_ASM
OPENSSLDIR: "/usr/lib/ssl"
How do I get a list of the available commands?
There are three built-in options for getting lists of available commands, but none of them provide what I consider useful output. The best thing to do is provide an invalid command (help or -h will do nicely) to get a readable answer.

$ openssl help
openssl:Error: 'help' is an invalid command.

Standard commands
asn1parse ca ciphers crl crl2pkcs7
dgst dh dhparam dsa dsaparam
ec ecparam enc engine errstr
gendh gendsa genrsa nseq ocsp
passwd pkcs12 pkcs7 pkcs8 prime
rand req rsa rsautl s_client
s_server s_time sess_id smime speed
spkac verify version x509

Message Digest commands (see the `dgst' command for more details)
md2 md4 md5 rmd160 sha
sha1

Cipher commands (see the `enc' command for more details)
aes-128-cbc aes-128-ecb aes-192-cbc aes-192-ecb aes-256-cbc
aes-256-ecb base64 bf bf-cbc bf-cfb
bf-ecb bf-ofb cast cast-cbc cast5-cbc
cast5-cfb cast5-ecb cast5-ofb des des-cbc
des-cfb des-ecb des-ede des-ede-cbc des-ede-cfb
des-ede-ofb des-ede3 des-ede3-cbc des-ede3-cfb des-ede3-ofb
des-ofb des3 desx rc2 rc2-40-cbc
rc2-64-cbc rc2-cbc rc2-cfb rc2-ecb rc2-ofb
rc4 rc4-40
What the shell calls “Standard commands” are the main top-level options.

You can use the same trick with any of the subcommands.

$ openssl dgst -h
unknown option '-h'
options are
-c to output the digest with separating colons
-d to output debug info
-hex output as hex dump
-binary output in binary form
-sign file sign digest using private key in file
-verify file verify a signature using public key in file
-prverify file verify a signature using private key in file
-keyform arg key file format (PEM or ENGINE)
-signature file signature to verify
-binary output in binary form
-engine e use engine e, possibly a hardware device.
-md5 to use the md5 message digest algorithm (default)
-md4 to use the md4 message digest algorithm
-md2 to use the md2 message digest algorithm
-sha1 to use the sha1 message digest algorithm
-sha to use the sha message digest algorithm
-sha256 to use the sha256 message digest algorithm
-sha512 to use the sha512 message digest algorithm
-mdc2 to use the mdc2 message digest algorithm
-ripemd160 to use the ripemd160 message digest algorithm
In more boring fashion, you can consult the OpenSSL man pages.

How do I get a list of available ciphers?
Use the ciphers option. The ciphers(1) man page is quite helpful.

# list all available ciphers
openssl ciphers -v

# list only TLSv1 ciphers
openssl ciphers -v -tls1

# list only high encryption ciphers (keys larger than 128 bits)
openssl ciphers -v 'HIGH'

# list only high encryption ciphers using the AES algorithm
openssl ciphers -v 'AES+HIGH'
Benchmarking
How do I benchmark my system’s performance?
The OpenSSL developers have built a benchmarking suite directly into the openssl binary. It’s accessible via the speed option. It tests how many operations it can perform in a given time, rather than how long it takes to perform a given number of operations. This strikes me a quite sane, because the benchmarks don’t take significantly longer to run on a slow system than on a fast one.

To run a catchall benchmark, run it without any further options.

openssl speed
There are two sets of results. The first reports how many bytes per second can be processed for each algorithm, the second the times needed for sign/verify cycles. Here are the results on an 2.16GHz Intel Core 2.

The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
md2 1736.10k 3726.08k 5165.04k 5692.28k 5917.35k
mdc2 0.00 0.00 0.00 0.00 0.00
md4 18799.87k 65848.23k 187776.43k 352258.73k 474622.63k
md5 16807.01k 58256.45k 160439.13k 287183.53k 375220.91k
hmac(md5) 23601.24k 74405.08k 189993.05k 309777.75k 379431.59k
sha1 16774.59k 55500.39k 142628.69k 233247.74k 288382.98k
rmd160 13854.71k 40271.23k 87613.95k 124333.06k 141781.67k
rc4 227935.60k 253366.06k 261236.94k 259858.09k 194928.50k
des cbc 48478.10k 49616.16k 49765.21k 50106.71k 50034.01k
des ede3 18387.39k 18631.02k 18699.26k 18738.18k 18718.72k
idea cbc 0.00 0.00 0.00 0.00 0.00
rc2 cbc 19247.24k 19838.12k 19904.51k 19925.33k 19834.98k
rc5-32/12 cbc 0.00 0.00 0.00 0.00 0.00
blowfish cbc 79577.50k 83067.03k 84676.78k 84850.01k 85063.00k
cast cbc 45362.14k 48343.34k 49007.36k 49202.52k 49225.73k
aes-128 cbc 58751.94k 94443.86k 111424.09k 116704.26k 117997.57k
aes-192 cbc 53451.79k 82076.22k 94609.83k 98496.85k 99150.51k
aes-256 cbc 49225.21k 72779.84k 82266.88k 85054.81k 85762.05k
sha256 9359.24k 22510.83k 40963.75k 51710.29k 56014.17k
sha512 7026.78k 28121.32k 54330.79k 86190.76k 104270.51k
sign verify sign/s verify/s
rsa 512 bits 0.000522s 0.000042s 1915.8 23969.9
rsa 1024 bits 0.002321s 0.000109s 430.8 9191.1
rsa 2048 bits 0.012883s 0.000329s 77.6 3039.6
rsa 4096 bits 0.079055s 0.001074s 12.6 931.3
sign verify sign/s verify/s
dsa 512 bits 0.000380s 0.000472s 2629.3 2117.9
dsa 1024 bits 0.001031s 0.001240s 969.6 806.2
dsa 2048 bits 0.003175s 0.003744s 314.9 267.1
You can run any of the algorithm-specific subtests directly.

# test rsa speeds
openssl speed rsa

# do the same test on a two-way SMP system
openssl speed rsa -multi 2
How do I benchmark remote connections?
The s_time option lets you test connection performance. The most simple invocation will run for 30 seconds, use any cipher, and use SSL handshaking to determine number of connections per second, using both new and reused sessions:

openssl s_time -connect remote.host:443
Beyond that most simple invocation, s_time gives you a wide variety of testing options.

# retrieve remote test.html page using only new sessions
openssl s_time -connect remote.host:443 -www /test.html -new

# similar, using only SSL v3 and high encryption (see
# ciphers(1) man page for cipher strings)
openssl s_time \
-connect remote.host:443 -www /test.html -new \
-ssl3 -cipher HIGH

# compare relative performance of various ciphers in
# 10-second tests
IFS=":"
for c in $(openssl ciphers -ssl3 RSA); do
echo $c
openssl s_time -connect remote.host:443 \
-www / -new -time 10 -cipher $c 2>&1 | \
grep bytes
echo
done
If you don’t have an SSL-enabled web server available for your use, you can emulate one using the s_server option.

# on one host, set up the server (using default port 4433)
openssl s_server -cert mycert.pem -www

# on second host (or even the same one), run s_time
openssl s_time -connect myhost:4433 -www / -new -ssl3
Certificates
How do I generate a self-signed certificate?
You’ll first need to decide whether or not you want to encrypt your key. Doing so means that the key is protected by a passphrase.

On the plus side, adding a passphrase to a key makes it more secure, so the key is less likely to be useful to someone who steals it. The downside, however, is that you’ll have to either store the passphrase in a file or type it manually every time you want to start your web or ldap server.

It violates my normally paranoid nature to say it, but I prefer unencrypted keys, so I don’t have to manually type a passphrase each time a secure daemon is started. (It’s not terribly difficult to decrypt your key if you later tire of typing a passphrase.)

This example will produce a file called mycert.pem which will contain both the private key and the public certificate based on it. The certificate will be valid for 365 days, and the key (thanks to the -nodes option) is unencrypted.

openssl req \
-x509 -nodes -days 365 \
-newkey rsa:1024 -keyout mycert.pem -out mycert.pem
Using this command-line invocation, you’ll have to answer a lot of questions: Country Name, State, City, and so on. The tricky question is “Common Name.” You’ll want to answer with the hostname or CNAME by which people will address the server. This is very important. If your web server’s real hostname is mybox.mydomain.com but people will be using www.mydomain.com to address the box, then use the latter name to answer the “Common Name” question.

Once you’re comfortable with the answers you provide to those questions, you can script the whole thing by adding the -subj option. I’ve included some information about location into the example that follows, but the only thing you really need to include for the certificate to be useful is the hostname (CN).

openssl req \
-x509 -nodes -days 365 \
-subj '/C=US/ST=Oregon/L=Portland/CN=www.madboa.com' \
-newkey rsa:1024 -keyout mycert.pem -out mycert.pem
How do I generate a certificate request for VeriSign?
Applying for a certificate signed by a recognized certificate authority like VeriSign is a complex bureaucratic process. You’ve got to perform all the requisite paperwork before creating a certificate request.

As in the recipe for creating a self-signed certificate, you’ll have to decide whether or not you want a passphrase on your private key. The recipe below assumes you don’t. You’ll end up with two files: a new private key called mykey.pem and a certificate request called myreq.pem.

openssl req \
-new -newkey rsa:1024 -nodes \
-keyout mykey.pem -out myreq.pem
If you’ve already got a key and would like to use it for generating the request, the syntax is a bit simpler.

openssl req -new -key mykey.pem -out myreq.pem
Similarly, you can also provide subject information on the command line.

openssl req \
-new -newkey rsa:1024 -nodes \
-subj '/CN=www.mydom.com/O=My Dom, Inc./C=US/ST=Oregon/L=Portland' \
-keyout mykey.pem -out myreq.pem
When dealing with an institution like VeriSign, you need to take special care to make sure that the information you provide during the creation of the certificate request is exactly correct. I know from personal experience that even a difference as trivial as substituting “and” for “&” in the Organization Name will stall the process.

If you’d like, you can double check the signature and information provided in the certificate request.

# verify signature
openssl req -in myreq.pem -noout -verify -key mykey.pem

# check info
openssl req -in myreq.pem -noout -text
Save the key file in a secure location. You’ll need it in order to use the certificate VeriSign sends you. The certificate request will typically be pasted into VeriSign’s online application form.

How do I test a new certificate?
The s_server option provides a simple but effective testing method. The example below assumes you’ve combined your key and certificate into one file called mycert.pem.

First, launch the test server on the machine on which the certificate will be used. By default, the server will listen on port 4433; you can alter that using the -accept option.

openssl s_server -cert mycert.pem -www
If the server launches without complaint, then chances are good that the certificate is ready for production use.

You can also point your web browser at the test server, e.g., https://yourserver:4433/. Don’t forget to specify the “https” protocol; plain-old “http” won’t work. You should see a page listing the various ciphers available and some statistics about your connection. Most modern browsers allow you to examine the certificate as well.

How do I retrieve a remote certificate?
If you combine openssl and sed, you can retrieve remote certificates via a shell one-liner or a simple script.

#!/bin/sh
#
# usage: retrieve-cert.sh remote.host.name [port]
#
REMHOST=$1
REMPORT=${2:-443}

echo |\
openssl s_client -connect ${REMHOST}:${REMPORT} 2>&1 |\
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
You can, in turn, pipe that information back to openssl to do things like check the dates on all your active certificates.

#!/bin/sh
#
for CERT in \
www.yourdomain.com:443 \
ldap.yourdomain.com:636 \
imap.yourdomain.com:993 \
do
echo |\
openssl s_client -connect ${CERT} 2>/dev/null |\
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' |\
openssl x509 -noout -subject -dates
done
How do I extract information from a certificate?
An SSL certificate contains a wide range of information: issuer, valid dates, subject, and some hardcore crypto stuff. The x509 subcommand is the entry point for retrieving this information. The examples below all assume that the certificate you want to examine is stored in a file named cert.pem.

Using the -text option will give you the full breadth of information.

openssl x509 -text -in cert.pem
Other options will provide more targeted sets of data.

# who issued the cert?
openssl x509 -noout -in cert.pem -issuer

# to whom was it issued?
openssl x509 -noout -in cert.pem -subject

# for what dates is it valid?
openssl x509 -noout -in cert.pem -dates

# the above, all at once
openssl x509 -noout -in cert.pem -issuer -subject -dates

# what is its hash value?
openssl x509 -noout -in cert.pem -hash

# what is its MD5 fingerprint?
openssl x509 -noout -in cert.pem -fingerprint
How do I export or import a PKCS#12 certificate?
PKCS#12 files can be imported and exported by a number of applications, including Microsoft IIS. They are often associated with the file extension .pfx.

To create a PKCS#12 certificate, you’ll need a private key and a certificate. During the conversion process, you’ll be given an opportunity to put an “Export Password” (which can be empty, if you choose) on the certificate.

# create a file containing key and self-signed certificate
openssl req \
-x509 -nodes -days 365 \
-newkey rsa:1024 -keyout mycert.pem -out mycert.pem

# export mycert.pem as PKCS#12 file, mycert.pfx
openssl pkcs12 -export \
-out mycert.pfx -in mycert.pem \
-name "My Certificate"
If someone sends you a PKCS#12 and any passwords needed to work with it, you can export it into standard PEM format.

# export certificate and passphrase-less key
openssl pkcs12 -in mycert.pfx -out mycert.pem -nodes

# same as above, but you’ll be prompted for a passphrase for
# the private key
openssl pkcs12 -in mycert.pfx -out mycert.pem
Certificate Verification
Applications linked against the OpenSSL libraries can verify certificates signed by a recognized certificate authority (CA).

How do I verify a certificate?
Use the verify option to verify certificates.

openssl verify cert.pem
If your local OpenSSL installation recognizes the certificate or its signing authority and everything else (dates, signing chain, etc.) checks out, you’ll get a simple OK message.

$ openssl verify remote.site.pem
remote.site.pem: OK
If anything is amiss, you’ll see some error messages with short descriptions of the problem, e.g.,

•error 10 at 0 depth lookup:certificate has expired. Certificates are typically issued for a limited period of time—usually just one year—and openssl will complain if a certificate has expired.

•error 18 at 0 depth lookup:self signed certificate. Unless you make an exception, OpenSSL won’t verify a self-signed certificate.

What certificate authorities does OpenSSL recognize?
When OpenSSL was built for your system, it was configured with a “Directory for OpenSSL files.” (That’s the --openssldir option passed to the configure script, for you hands-on types.) This is the directory that typically holds information about certificate authorities your system trusts.

The default location for this directory is /usr/local/ssl, but most vendors put it elsewhere, e.g., /usr/share/ssl (Red Hat/Fedora), /etc/ssl (Gentoo), /usr/lib/ssl (Debian), or /System/Library/OpenSSL (Macintosh OS X).

Use the version option to identify which directory (labeled OPENSSLDIR) your installation uses.

openssl version -d
Within that directory and a subdirectory called certs, you’re likely to find one or more of three different kinds of files.

1.A large file called cert.pem, an omnibus collection of many certificates from recognized certificate authorities like VeriSign and Thawte.

2.Some small files in the certs subdirectory named with a .pem file extension, each of which contains a certificate from a single CA.

3.Some symlinks in the certs subdirectory with obscure filenames like 052eae11.0. There is typically one of these links for each .pem file.

The first part of obscure filename is actually a hash value based on the certificate within the .pem file to which it points. The file extension is just an iterator, since it’s theoretically possible that multiple certificates can generate identical hashes.

On my Gentoo system, for example, there’s a symlink named f73e89fd.0 that points to a file named vsignss.pem. Sure enough, the certificate in that file generates a hash the equates to the name of the symlink:

$ openssl x509 -noout -hash -in vsignss.pem
f73e89fd
When an application encounters a remote certificate, it will typically check to see if the cert can be found in cert.pem or, if not, in a file named after the certificate’s hash value. If found, the certificate is considered verified.

It’s interesting to note that some applications, like Sendmail, allow you to specify at runtime the location of the certificates you trust, while others, like Pine, do not.

How do I get OpenSSL to recognize/verify a certificate?
Put the file that contains the certificate you’d like to trust into the certs directory discussed above. Then create the hash-based symlink. Here’s a little script that’ll do just that.

#!/bin/sh
#
# usage: certlink.sh filename [filename ...]

for CERTFILE in $*; do
# make sure file exists and is a valid cert
test -f "$CERTFILE" || continue
HASH=$(openssl x509 -noout -hash -in "$CERTFILE")
test -n "$HASH" || continue

# use lowest available iterator for symlink
for ITER in 0 1 2 3 4 5 6 7 8 9; do
test -f "${HASH}.${ITER}" && continue
ln -s "$CERTFILE" "${HASH}.${ITER}"
test -L "${HASH}.${ITER}" && break
done
done
Command-line clients and servers
The s_client and s_server options provide a way to launch SSL-enabled command-line clients and servers. There are other examples of their use scattered around this document, but this section is dedicated solely to them.

In this section, I assume you are familiar with the specific protocols at issue: SMTP, HTTP, etc. Explaining them is out of the scope of this article.

How do I connect to a secure SMTP server?
You can test, or even use, an SSL-enabled SMTP server from the command line using the s_client option.

Secure SMTP servers offer secure connections on up to three ports: 25 (TLS), 465 (SSL), and 587 (TLS). Some time around the 0.9.7 release, the openssl binary was given the ability to use STARTTLS when talking to SMTP servers.

# port 25/TLS; use same syntax for port 587
openssl s_client -connect remote.host:25 -starttls smtp

# port 465/SSL
openssl s_client -connect remote.host:465
RFC821 suggests (although it falls short of explicitly specifying) the two characters "" as line-terminator. Most mail agents do not care about this and accept either "" or "" as line-terminators, but Qmail does not. If you want to comply to the letter with RFC821 and/or communicate with Qmail, use also the -crlf option:

openssl s_client -connect remote.host:25 -crlf -starttls smtp
How do I connect to a secure [whatever] server?
Connecting to a different type of SSL-enabled server is essentially the same operation as outlined above. As of the date of this writing, openssl only supports command-line TLS with SMTP servers, so you have to use straightforward SSL connections with any other protocol.

# https: HTTP over SSL
openssl s_client -connect remote.host:443

# ldaps: LDAP over SSL
openssl s_client -connect remote.host:636

# imaps: IMAP over SSL
openssl s_client -connect remote.host:993

# pop3s: POP-3 over SSL
openssl s_client -connect remote.host:995
How do I set up an SSL server from the command line?
The s_server option allows you to set up an SSL-enabled server from the command line, but it’s I wouldn’t recommend using it for anything other than testing or debugging. If you need a production-quality wrapper around an otherwise insecure server, check out Stunnel instead.

The s_server option works best when you have a certificate; it’s fairly limited without one.

# the -www option will sent back an HTML-formatted status page
# to any HTTP clients that request a page
openssl s_server -cert mycert.pem -www

# the -WWW option "emulates a simple web server. Pages will be
# resolved relative to the current directory." This example
# is listening on the https port, rather than the default
# port 4433
openssl s_server -accept 443 -cert mycert.pem -WWW
Digests
Generating digests with the dgst option is one of the more straightforward tasks you can accomplish with the openssl binary. Producing digests is done so often, as a matter of fact, that you can find special-use binaries for doing the same thing.

How do I create an MD5 or SHA1 digest of a file?
Digests are created using the dgst option.

# MD5 digest
openssl dgst -md5 filename

# SHA1 digest
openssl dgst -sha1 filename
The MD5 digests are identical to those created with the widely available md5sum command, though the output formats differ.

$ openssl dgst -md5 foo-2.23.tar.gz
MD5(foo-2.23.tar.gz)= 81eda7985e99d28acd6d286aa0e13e07
$ md5sum foo-2.23.tar.gz
81eda7985e99d28acd6d286aa0e13e07 foo-2.23.tar.gz
The same is true for SHA1 digests and the output of the sha1sum application.

$ openssl dgst -sha1 foo-2.23.tar.gz
SHA1(foo-2.23.tar.gz)= e4eabc78894e2c204d788521812497e021f45c08
$ sha1sum foo-2.23.tar.gz
e4eabc78894e2c204d788521812497e021f45c08 foo-2.23.tar.gz
How do I sign a digest?
If you want to ensure that the digest you create doesn’t get modified without your permission, you can sign it using your private key. The following example assumes that you want to sign the SHA1 sum of a file called foo-1.23.tar.gz.

# signed digest will be foo-1.23.tar.gz.sha1
openssl dgst -sha1 \
-sign mykey.pem
-out foo-1.23.tar.gz.sha1 \
foo-1.23.tar.gz
How do I verify a signed digest?
To verify a signed digest you’ll need the file from which the digest was derived, the signed digest, and the signer’s public key.

# to verify foo-1.23.tar.gz using foo-1.23.tar.gz.sha1
# and pubkey.pem
openssl dgst -sha1 \
-verify pubkey.pem \
-signature foo-1.23.tar.gz.sha1 \
foo-1.23.tar.gz
How do I create an Apache digest password entry?
Apache’s HTTP digest authentication feature requires a special password format. Apache ships with the htdigest utility, but it will only write to a file, not to standard output. When working with remote users, it’s sometimes nice for them to be able to generate a password hash on a machine they trust and then mail it for inclusion in your local password database.

The format of the password database is relatively simple: a colon-separated list of the username, authorization realm (specified by the Apache AuthName directive), and an MD5 digest of those two items and the password. Below is a script that duplicates the output of htdigest, except that the output is written to standard output. It takes advantage of the dgst option’s ability to read from standard input.

#!/bin/bash

echo "Create an Apache-friendly Digest Password Entry"
echo "-----------------------------------------------"

# get user input, disabling tty echoing for password
read -p "Enter username: " UNAME
read -p "Enter Apache AuthName: " AUTHNAME
read -s -p "Enter password: " PWORD; echo

printf "\n%s:%s:%s\n" \
"$UNAME" \
"$AUTHNAME" \
$(printf "${UNAME}:${AUTHNAME}:${PWORD}" | openssl dgst -md5)
What other kinds of digests are available?
Use the built-in list-message-digest-commands option to get a list of the digest types available to your local OpenSSL installation.

openssl list-message-digest-commands
Encryption/Decryption
How do I base64-encode something?
Use the enc -base64 option.

# send encoded contents of file.txt to stdout
openssl enc -base64 -in file.txt

# same, but write contents to file.txt.enc
openssl enc -base64 -in file.txt -out file.txt.enc
It’s also possible to do a quick command-line encoding of a string value:

$ echo "encode me" | openssl enc -base64
ZW5jb2RlIG1lCg==
Note that echo will silently attach a newline character to your string. Consider using its -n option if you want to avoid that situation, which could be important if you’re trying to encode a password or authentication string.

$ echo -n "encode me" | openssl enc -base64
ZW5jb2RlIG1l
Use the -d (decode) option to reverse the process.

$ echo "ZW5jb2RlIG1lCg==" | openssl enc -base64 -d
encode me
How do I simply encrypt a file?
Simple file encryption is probably better done using a tool like GPG. Still, you may have occasion to want to encrypt a file without having to build or use a key/certificate structure. All you want to have to remember is a password. It can nearly be that simple—if you can also remember the cipher you employed for encryption.

To choose a cipher, consult the enc(1) man page. More simply (and perhaps more accurately), you can ask openssl for a list in one of two ways.

# see the list under the 'Cipher commands' heading
openssl -h

# or get a long list, one cipher per line
openssl list-cipher-commands
After you choose a cipher, you’ll also have to decide if you want to base64-encode the data. Doing so will mean the encrypted data can be, say, pasted into an email message. Otherwise, the output will be a binary file.

# encrypt file.txt to file.enc using 256-bit AES in CBC mode
openssl enc -aes-256-cbc -salt -in file.txt -out file.enc

# the same, only the output is base64 encoded for, e.g., e-mail
openssl enc -aes-256-cbc -a -salt -in file.txt -out file.enc
To decrypt file.enc you or the file’s recipient will need to remember the cipher and the passphrase.

# decrypt binary file.enc
openssl enc -d -aes-256-cbc -in file.enc

# decrypt base64-encoded version
openssl enc -d -aes-256-cbc -a -in file.enc
If you’d like to avoid typing a passphrase every time you encrypt or decrypt a file, the openssl(1) man page provides the details under the heading “PASS PHRASE ARGUMENTS.” The format of the password argument is fairly simple.

# provide password on command line
openssl enc -aes-256-cbc -salt -in file.txt \
-out file.enc -pass pass:mySillyPassword

# provide password in a file
openssl enc -aes-256-cbc -salt -in file.txt \
-out file.enc -pass file:/path/to/secret/password.txt
Errors
How do I interpret SSL error messages?
Poking through your system logs, you see some error messages that are evidently related to OpenSSL or crypto:

sshd[31784]: error: RSA_public_decrypt failed: error:0407006A:lib(4):func(112):reason(106)
sshd[770]: error: RSA_public_decrypt failed: error:0407006A:lib(4):func(112):reason(106)
The first step to figure out what’s going wrong is to use the errstr option to intrepret the error code. The code number is found between “error:” and “:lib”. In this case, it’s 0407006A.

$ openssl errstr 0407006A
error:0407006A:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01
If you’ve got a full OpenSSL installation, including all the development documentation, you can start your investigation there. In this example, the RSA_padding_add_PKCS1_type_1(3) man page will inform you that PKCS #1 involves block methods for signatures. After that, of course, you’d need to pore through your application’s source code to identify when it would expect be receiving those sorts of packets.

Keys
How do I generate an RSA key?
Use the genrsa option.

# default 512-bit key, sent to standard output
openssl genrsa

# 1024-bit key, saved to file named mykey.pem
openssl genrsa -out mykey.pem 1024

# same as above, but encrypted with a passphrase
openssl genrsa -des3 -out mykey.pem 1024
How do I generate a public RSA key?
Use the rsa option to produce a public version of your private RSA key.

openssl rsa -in mykey.pem -pubout
How do I generate a DSA key?
Building DSA keys requires a parameter file, and DSA verify operations are slower than their RSA counterparts, so they aren’t as widely used as RSA keys.

If you’re only going to build a single DSA key, you can do so in just one step using the dsaparam subcommand.

# key will be called dsakey.pem
openssl dsaparam -noout -out dsakey.pem -genkey 1024
If, on the other hand, you’ll be creating several DSA keys, you’ll probably want to build a shared parameter file before generating the keys. It can take a while to build the parameters, but once built, key generation is done quickly.

# create parameters in dsaparam.pem
openssl dsaparam -out dsaparam.pem 1024

# create first key
openssl gendsa -out key1.pem dsaparam.pem

# and second ...
openssl gendsa -out key2.pem dsaparam.pem
How do I create an elliptic curve key?
Routines for working with elliptic curve cryptography were added to OpenSSL in version 0.9.8. Generating an EC key involves the ecparam option.

openssl ecparam -out key.pem -name prime256v1 -genkey

# openssl can provide full list of EC parameter names suitable for
# passing to the -name option above:
openssl ecparam -list_curves
How do I remove a passphrase from a key?
Perhaps you’ve grown tired of typing your passphrase every time your secure daemon starts. You can decrypt your key, removing the passphrase requirement, using the rsa or dsa option, depending on the signature algorithm you chose when creating your private key.

If you created an RSA key and it is stored in a standalone file called key.pem, then here’s how to output a decrypted version of the same key to a file called newkey.pem.

# you'll be prompted for your passphrase one last time
openssl rsa -in key.pem -out newkey.pem
Often, you’ll have your private key and public certificate stored in the same file. If they are stored in a file called mycert.pem, you can construct a decrypted version called newcert.pem in two steps.

# you'll need to type your passphrase once more
openssl rsa -in mycert.pem -out newcert.pem
openssl x509 -in mycert.pem >>newcert.pem
Password hashes
Using the passwd option, you can generate password hashes that interoperate with traditional /etc/passwd files, newer-style /etc/shadow files, and Apache password files.

How do I generate a crypt-style password hash?
You can generate a new hash quite simply:

$ openssl passwd MySecret
8E4vqBR4UOYF.
If you know an existing password’s “salt,” you can duplicate the hash.

$ openssl passwd -salt 8E MySecret
8E4vqBR4UOYF.
How do I generate a shadow-style password hash?
Newer Unix systems use a more secure MD5-based hashing mechanism that uses an eight-character salt (as compared to the two-character salt in traditional crypt()-style hashes). Generating them is still straightforward using the -1 option:

$ openssl passwd -1 MySecret
$1$sXiKzkus$haDZ9JpVrRHBznY5OxB82.
The salt in this format consists of the eight characters between the second and third dollar signs, in this case sXiKzkus. So you can also duplicate a hash with a known salt and password.

$ openssl passwd -1 -salt sXiKzkus MySecret
$1$sXiKzkus$haDZ9JpVrRHBznY5OxB82.
Prime numbers
Current cryptographic techniques rely heavily on the generation and testing of prime numbers, so it’s no surprise that the OpenSSL libraries contain several routines dealing with primes. Beginning with version 0.9.7e (or so), the prime option was added to the openssl binary.

How do I test whether a number is prime?
Pass the number to the prime option. Note that the number returned by openssl will be in hex, not decimal, format.

$ openssl prime 119054759245460753
1A6F7AC39A53511 is not prime
You can also pass hex numbers directly.

$ openssl prime -hex 2f
2F is prime
How do I generate a set of prime numbers?
Pass a bunch of numbers to openssl and see what sticks. The seq utility is useful in this capacity.

# define start and ending points
AQUO=10000
ADQUEM=10100
for N in $(seq $AQUO $ADQUEM); do
# use bc to convert hex to decimal
openssl prime $N | awk '/is prime/ {print "ibase=16;"$1}' | bc
done
Random data
How do I generate random data?
Use the rand option to generate binary or base64-encoded data.

# write 128 random bytes of base64-encoded data to stdout
openssl rand -base64 128

# write 1024 bytes of binary random data to a file
openssl rand -out random-data.bin 1024

# seed openssl with semi-random bytes from browser cache
cd $(find ~/.mozilla/firefox -type d -name Cache)
openssl rand -rand $(find . -type f -printf '%f:') -base64 1024
On a Unix box with a /dev/urandom device and a copy of GNU head, you can achieve a similar effect, often with better entropy:

# get 32 bytes from /dev/urandom and base64 encode them
head -c 32 /dev/urandom | openssl enc -base64
Make sure you know the trade-offs between the random and urandom devices before relying on them for truly critical entropy. Consult the random(4) man page on Linux and BSD systems, or random(7D) on Solaris, for further information.

S/MIME
S/MIME is a standard for sending and receiving secure MIME data, especially in e-mail messages. Automated S/MIME capabilities have been added to quite a few e-mail clients, though openssl can provide command-line S/MIME services using the smime option.

Note that the documentation in the smime(1) man page includes a number of good examples.

How do I verify a signed S/MIME message?
It’s pretty easy to verify a signed message. Use your mail client to save the signed message to a file. In this example, I assume that the file is named msg.txt.

openssl smime -verify -in msg.txt
If the sender’s certificate is signed by a certificate authority trusted by your OpenSSL infrastructure, you’ll see some mail headers, a copy of the message, and a concluding line that says Verification successful.

If the messages has been modified by an unauthorized party, the output will conclude with a failure message indicating that the digest and/or the signature doesn’t match what you received:

Verification failure
23016:error:21071065:PKCS7 routines:PKCS7_signatureVerify:digest
failure:pk7_doit.c:804:
23016:error:21075069:PKCS7 routines:PKCS7_verify:signature
failure:pk7_smime.c:265:
Likewise, if the sender’s certificate isn’t recognized by your OpenSSL infrastructure, you’ll get a similar error:

Verification failure
9544:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify
error:pk7_smime.c:222:Verify error:self signed certificate
Most e-mail clients send a copy of the public certificate in the signature attached to the message. From the command line, you can view the certificate data yourself. You’ll use the smime -pk7out option to pipe a copy of the PKCS#7 certificate back into the pkcs7 option. It’s oddly cumbersome but it works.

openssl smime -pk7out -in msg.txt | \
openssl pkcs7 -text -noout -print_certs
If you’d like to extract a copy of your correspondent’s certificate for long-term use, use just the first part of that pipe.

openssl smime -pk7out -in msg.txt -out her-cert.pem
At that point, you can either integrate it into your OpenSSL infrastructure or you can save it off somewhere for special use.

openssl smime -verify -in msg.txt -CAfile /path/to/her-cert.pem
How do I encrypt a S/MIME message?
Let’s say that someone sends you her public certificate and asks that you encrypt some message to her. You’ve saved her certificate as her-cert.pem. You’ve saved your reply as my-message.txt.

To get the default—though fairly weak—RC2-40 encryption, you just tell openssl where the message and the certificate are located.

openssl smime her-cert.pem -encrypt -in my-message.txt
If you’re pretty sure your remote correspondent has a robust SSL toolkit, you can specify a stronger encryption algorithm like triple DES:

openssl smime her-cert.pem -encrypt -des3 -in my-message.txt
By default, the encrypted message, including the mail headers, is sent to standard output. Use the -out option or your shell to redirect it to a file. Or, much trickier, pipe the output directly to sendmail.

openssl smime her-cert.pem \
-encrypt \
-des3 \
-in my-message.txt \
-from 'Your Fullname ' \
-to 'Her Fullname ' \
-subject 'My encrypted reply' |\
sendmail her@heraddress.com
How do I sign a S/MIME message?
If you don’t need to encrypt the entire message, but you do want to sign it so that your recipient can be assured of the message’s integrity, the recipe is similar to that for encryption. The main difference is that you need to have your own key and certificate, since you can’t sign anything with the recipient’s cert.

openssl smime \
-sign \
-signer /path/to/your-cert.pem \
-in my-message.txt \
-from 'Your Fullname ' \
-to 'Her Fullname ' \
-subject 'My signed reply' |\
sendmail her@heraddress.com
For further reading
Though it takes time to read them all and figure out how they relate to one another, the OpenSSL man pages are the best place to start: asn1parse(1), ca(1), ciphers(1), config(5), crl(1), crl2pkcs7(1), dgst(1), dhparam(1), dsa(1), dsaparam(1), ec(1), ecparam(1), enc(1), errstr(1), gendsa(1), genpkey(1), genrsa(1), nseq(1), ocsp(1), openssl(1), passwd(1), pkcs12(1), pkcs7(1), pkcs8(1), pkey(1), pkeyparam(1), pkeyutl(1), rand(1), req(1), rsa(1), rsautl(1), s_client(1), s_server(1), s_time(1), sess_id(1), smime(1), speed(1), spkac(1), ts(1), tsget(1), verify(1), version(1), x509(1), x509v3_config(5).

Comments welcome
Comments and suggestions about this document are appreciated and can be addressed to the author at .
This article is licensed under a Creative Commons License.
Return to Technical Writings

Home - Tech - Praise - Paul - Books - About

printer-friendly layout

Simple File Encryption with OpenSSL

Simple File Encryption with OpenSSL


Published in December 12th, 2007
Posted by Tom in software

Linux has plenty of powerful encryption software, but what can you use if you just want to secure a couple files quickly? The OpenSSL toolkit works well for this. It comes installed with Ubuntu and can provide stronger encryption than you would ever need.

This is the basic command to encrypt a file:

openssl aes-256-cbc -a -salt -in secrets.txt -out secrets.txt.enc

How does this work?

•openssl is the command for the OpenSSL toolkit.
•aes-256-cbc is the encryption cipher to be used. (256bit AES is what the United States government uses to encrypt information at the Top Secret level.)
•-a means that the encrypted output will be base64 encoded, this allows you to view it in a text editor or paste it in an email. This is optional.
•-salt adds strength to the encryption and should always be used.
•-in secrets.txt specifies the input file.
•-out secrets.txt.enc specifies the output file.
•You will be prompted for a password.
It’s not much use unless you can decrypted it:

openssl aes-256-cbc -d -a -in secrets.txt.enc -out secrets.txt.new

•-d decrypts data.
•-a tells OpenSSL that the encrypted data is in base64.
•-in secrets.txt.enc specifies the data to decrypt.
•-out secrets.txt.new specifies the file to put the decrypted data in.
Try out OpenSSL by decrypting this string (the password is pass):

U2FsdGVkX18YcWkbmhsN7M/MP1E+GLf4IqmNsa53T+A=

You can paste it into a text file and use the commands above, or use this command instead:

echo U2FsdGVkX18YcWkbmhsN7M/MP1E+GLf4IqmNsa53T+A= | openssl aes-256-cbc -d -a

See the OpenSSL man page for more detail on what it can do.

/dev/random 을 이용한 랜덤값 생성

/dev/random 을 이용한 랜덤값 생성


/dev/random 을 이용한 랜덤값 생성
윤 상배
dreamyun@yahoo.co.kr


교정 과정
교정 1.0 2004년 1월 28일 19시
/dev/random 장치 노이즈 수집 문제점
교정 0.9 2003년 2월 24일 22시
솔라리스에서 /dev/random 문자장치생성
교정 0.8 2003년 2월 9일 21시
문서 작성


--------------------------------------------------------------------------------

차례
1절. 소개
2절. RANDOM 값 만들기
2.1절. 왜 RANDOM값이 중요한가
2.2절. 표준 C random 함수
2.2.1절. 성능 테스트
2.3절. /dev/random 의 이용
2.3.1절. 조용한 시스템에서의 /dev/random 문제점
2.3.2절. 지원 OS 제한
2.3.2.1절. Sun OS 에서의 /dev/random 생성
3절. 결론

--------------------------------------------------------------------------------

1절. 소개
이번글은 리눅스 시스템에서 제공하는 문자장치(character devices)를 이용한 랜덤값을 얻어내는 방법에 대해서 담고 있다. Linux kernel 2.4.x 환경에서만 테스트되었으나, Kernel 2.2.x 에서도 동일하게 작동될것으로 생각된다. Solaris 의 경우 2.8 버전이후로 패치를 통해서 /dev/random 을 지원하는걸로 되어있다. 다른 Unix 들도 대부분 지원하지만 버젼에 따라서 지원여부가 결정될것이다.


--------------------------------------------------------------------------------

2절. RANDOM 값 만들기
2.1절. 왜 RANDOM값이 중요한가
random 의 의미가 "임의의", "일정치 않는"의 뜻을 가진다는 것은 누구든지 알고 있을것이다. 가장 간단한 랜덤값의 예는 주사위가 던져질경우 나오는 눈의 수 가 될것이며, 던지는 사람이 아무 생각없이 던질경우 "임의의" 값이 나오게 될것이다.

이 "임의의" 값은 일상생활에서 자주 사용되며, 특히 "보안"을 필요로 하는곳에서 더욱 중요하게 다루어진다. 금고의 문을 열기 위한 6자리의 숫자를 조합한다고 했을때, 카드에서 현금서비스등을 서비스받기 위해 사용하는 4자리 숫자의 조합등 "임이의" 값이 사용되어야 할것이다. 흔히 이러한 숫자조합을 만들때 가장 문제시 되는게, "임의의" 값을 사용하지 않고 숫자조합을 만든다는 점이다. 자기 생일이라든지, 아는 사람의 전화번호등이 대표적인 예로, 이런 값들은 "임의의"값이 아니다. 임의의 값이 아니란 뜻은 유추가 가능함을 뜻하며, 유추가 가능하다는 것은 그만큼 헛점이 많아질수 있음을 뜻한다.

컴퓨팅 환경에서도 이러한 "임의의"값 을 선택할수 있어야 한다. 선택된 임의의 값은 여러가지 용도로 사용될것인데, 대표적으로 사용할수 있는게 사용자 확인을 위한 "password" 와 SSL 과 같은 라이브러리등에서 암호화및 복호화를 위한 key값등의 제작일 것이다.

이러한 "임의의"값들은 당연하지만 최대한 "임의의"값으로써, 가능한 유추될수 없는 값이 되어야 할것이다. 만약 우리가 "임의의"값 을 얻기 위한 어떤 함수를 만들었고, 이 함수를 통해서 1-9999 사이의 임의의 값을 얻어내려고 하는데, 함수를 사용했더니 5000 - 6000 사이의 값이 다른 값보다 특별히 많이 나온다면, 이 함수는 믿을수 없는 "결함이 있는" 함수가 될것이며, 이 함수를 사용하는 많은 프로그램은 보안 결함을 가지게 될것이다. 이상적으로 각각의 값이 선택될 확률은 모두 동일(1/값의범위)해야 할것이다-다른말로 표준편차 0-.


--------------------------------------------------------------------------------

2.2절. 표준 C random 함수
표준 C 에서는 랜덤값의 계산을 위해서 random()와 srandom() 두개의 함수를 제공한다.

#include

long random(void);
void srandom(unsigned int seed);

srandom 함수는 random seed 값을 만들기 위해서 사용되며, random 은 만들어진 random seed 값을 이용해서 일련의 랜덤값을 발생시킨다. 이 말은 random함수를 이용해서 발생되는 랜덤값은 srandom 에 의존적임을 뜻하며, 실제 같은 random seed 를 이용해서 random 함수를 돌릴경우 언제나 동일한 일련의 랜덤값을 얻게 된다.

srandom 에서의 random seed 는 아규먼트로 주어지는 seed에 의해서 생성된다.

random 함수가 srandom 에서 만들어내는 random seed 에 의해서 랜덤값을 만들어낸다는 것은 그리 좋지 않은 아이디어라고 생각된다. 왜냐하면 seed 에 임의의 int 값을 할당한다는게 생각처럼 쉬운게 아니기 때문이며(보통은 컴퓨터의 시간값을 사용한다), 이러한 값은 유추될수 있기 때문이다. 기본적으로 동일한 seed 값을 이용할경우 동일한 일련의 랜덤값을 얻을수 있기 때문이다.

또한 random 으로 생성되는 랜덤값의 범위는 16*((2**31)-1) 이다. 언뜻보면 매우 큰숫자인것 같다. 그러나 최근 ssl 등에서 key 의 크기가 128bit 인것을 감안하면 너무 작은 범위의 랜덤값만을 얻어올수 있어서, 현재 컴퓨팅 환경이 요구하는 수준에 크게 미달되고 있음을 알수 있다.

즉 그리 복잡하거나 중요하지 않은 어플리케이션에서의 랜덤값을 만들기 위해서는 간단하게 사용가능하지만, 그렇지 않은 실제 서비스환경에서 사용하기에는 부족한점이 있다.


--------------------------------------------------------------------------------

2.2.1절. 성능 테스트
일단 random seed 를 제외하고 생각한다면, 랜덤값은 정말 랜덤하게 나와야 한다. 예를들어 1-100 까지의 범위에서 랜덤값을 추출하고자 했을때 이것을 100000번 돌리면 1-100 사이의 각각의 값이 거의 비슷한 횟수로 선택되어져야 할것이다. 약간더 많이 선택되거나 그렇지 않은 랜덤값이 있겠지만 대충 1000 정도에서 선택되어져야 할것이며, 이 오차 폭이 작을수록 성능이 좋은 랜덤 함수라고 할수 있다.

보통 이러한 통계수치에서 각각의 관측값이 평균에서 떨어진 정보를 가지고 얼마나 바람직하게 분포되어 있는지를 판단하게 되는데, 이를 표준편차라고 한다. 여기에서는 random 함수의 성능을 알아보기 위해서 표준편차를 구하고 이를 그래프로 확인해보도록 하겠다(통계 계산값은 숫자보다는 아무래도 그림이 이해하기가 쉽다).

이러한 표준편차를 구하기 위해서 간단한 테스트용 코드를 만들것이다. 이 코드는 srandom 을 이용해서 random seed 를 만들고, 이 random seed 를 통해서 random 을 100000번 돌리게 될것이다. 랜덤값의 범위는 1에서 100 사이가 될것이며, 각 관측값이 몇번씩 출력되는지를 확인하고, 이것을 이용해서 표준편차를 구하고, 그래프를 만들것이다. 참고로 표준편차를 구하는 일반적인 공식은 다음과 같다.

그림 1. 표준편차 공식




다음은 테스트를 위한 코드이다.

예제 : random_test.c

#include
#include
#include
#include

struct mdata
{
int count;
};

int main()
{
int i = 0;
struct mdata mydata[101];

int sum = 0;
int avg = 0;
int dosu = 0;
int dosu_p = 0;

memset((void *)&mydata, 0x00, sizeof(struct mdata)*101);
// srandom(100);

// 100000번동안 0-99 사이의 랜덤값을 얻어온다.
// 얻어온 랜덤값은 counting 된다.
while( i < 100000)
{
mydata[random()%100].count++;
i++;
}

i = 0;

// 카운팅된 랜덤값을 이용해서
// 평균,합,표준편차를 구해낸다.
while (i < 100)
{
sum += mydata[i].count;
printf("%d %d\n", i, mydata[i].count);
i++;
}
avg = sum/100;
printf("평균 : %d\n", avg);
printf("합 : %d\n", sum);

sum = 0;
i = 0;
while (i < 100)
{
sum += (mydata[i].count - avg)*(mydata[i].count - avg);
i++;
}
// sqrt(sum/100) 을 하면 표준편차가
// 나온다.
printf("%d\n", sum/100);
}

위의 실행결과를 보면 표준편차는 대략 36 정도가 나온다. 이말은 평균값인 1000 에서 대략 36정도의 범위내에 모든 관측값이 위치함을 뜻한다. 괜찮은 성능을 보여준다는걸 알수 있다. 아래는 실행결과이다. 96 1012
97 992
98 970
99 1008
평균 : 1000
합 : 100000
1303

마지막 출력값인 1303 에 sqrt 연산을 해주면 표준편차를 구할수 있다.

다음은 위의 코드를 돌려서 나온 결과를 그래프로 나타낸것인데, 평균값인 1000 부근에 대부분 위치하고 있음을 알수 있다. 이 그래프는 gnuplot 를 이용해서 만들어졌다.

그림 2. random 성능테스트 결과



random.dat 는 srandom 함수를 사용하지 않은 상태에서 기본 random seed 를 이용해서 만들어진 값이며, random2.dat 는 srandom 을 100 으로 한다음에 만들어진 값이다.



2.3절. /dev/random 의 이용
Unix 에서는 좀더 범용적으로 사용할수 있는 방법을 제공한다. /dev/random 이라는 문자장치를 통한 랜덤값가져오기가 이 방법이다.

이 문자장치는 커널에서 제공하는데, int 형의 값을 이용해서 random seed 를 생성해내는 random 함수 와는 달리 다른 장치드라이버와 엔트로피풀안의 다른 소스 들로 부터 노이즈를 모으고 이러한 노이즈와 장치드라이버에 걸리는 인터럽트시간 간격등을 이용해서 난수를 생성시킨다.

간단히 생각해서 키보드, 마우스, 디스크 혹은 내부적으로 발생되는 다른 인터럽트등을 이용해서 난수를 발생시킨다고 보면 된다. 이들 인터럽트 값등은 예측하기가 매우 힘들기 때문에 근본적으로 random 함수를 이용하는것보다 매우 안전하게 랜덤값을 생성할수가 있다. 또한 난수의 범위를 매우 크게 잡을수 있기 때문에, 128bit 크기를 기본으로 사용하는 지금의 컴퓨팅 환경에 유용하게 사용할수 있다

실제 openssl 과 같은 라이브러리등은 암호화된 key의 생성을 위해서 /dev/random 을 사용한다. 다음은 128bit 크기의 난수를 생성하는 간단한 예제 프로그램이다.

예제 : dev_mem.c

#include
#include
#include
#include
#include
#include
#include

int main()
{
int i, fd;
char key[16];
if ((fd = open("/dev/random", O_RDONLY)) == -1)
{
perror("open error");
exit(1);
}
if ((read(fd, key, 16)) == -1)
{
perror("read error");
exit(1);
}

for (i = 0; i < 16; i++)
{
printf("%c", key[i]);
}
}

위의 코드는 16 * 8(128)bit 크기를 가지는 랜덤값을 만들어낸다. 위프로그램을 실행시킨 결과값을 확인하기 좋게 만들기 위해서 mimecode 를 통해서 아래와 같이 출력해보았다. [root@localhost c_source]# ./dev_mem | mimencode
6qK3AlTHc0nUUETnoL5LRA==

mimencode 는 입력값을 base64 인코딩해서 그 결과를 출력하며, 보통 MIME 메시지를 첨부하기 위한 목적으로 사용되는 어플리케이션이다.

코드는 매우 간단하며, 실행시마다 서로 다른 랜덤값이 출력되는걸 확인할수 있을것이다. 또한 랜덤값의 크기 제한역시 매우 자유롭다. 위의 key 배열의 크기를 32 로 한다면 간단하게 256bit 크기를 가지는 함수를 생성할수 있다.


--------------------------------------------------------------------------------

2.3.1절. 조용한 시스템에서의 /dev/random 문제점
/dev/random을 사용하는데 있어서 사소한(때에 따라서는 심각한) 문제가 하나 있는데, 장치의 노이즈를 수집해서 앤트로피 풀에 저장하고 이 값을 이용해서 랜덤값을 만들어 낸다는 특징 때문에 장치에 노이즈가 없을 때는 앤트로피 풀이 비어 버리고, 때문에 매우 오랜 시간동안 랜덤값이 발생하지 않을 수 있다는 점이다.

다음의 코드를 테스트 해보기 바란다.

#include
#include
#include
#include
#include

#define MAX_RND_SIZE 32

int random_init()
{
int fd;
fd = open("/dev/random", O_RDONLY);
return fd;
}

int random_get(int fd, void *buf, size_t size)
{
int i = 0;
int n = 0;

// 주석 1.
while( n < size)
{
n += read(fd, buf, size - n);
}
return n;
}

int random_clear(int fd)
{
close(fd);
}
int main()
{
int fd;
int n;
unsigned int value;

fd = random_init();
sleep(5);
while(1)
{
n = random_get(fd, (void *)&value, 4);
printf("%d %lu\n", n, value);
}
random_clear(fd);
}

당신의 시스템이 조용한 상태라고 가정한다면 처음 몇 개는 발생하지만 그 후에는 띄엄띄엄 발생 하는 것을 확인할 수 있을 것이다. 자 이제 키보드를 눌러 보거나. 마우스를 움직여 보거나 복사와 같은 파일 관련 작업을 해보기 바란다. 아마 랜덤값이 빠르게 발생하는 걸 확인 할 수 있을 것이다.

이러한 /dev/random의 특징 때문에 연속해서 랜덤한 값을 얻고자 할 때 문제가 발생할 수 있으니 이럴 경우 사용에 주의해야 한다.(물론 그리 흔한 경우가 아니긴 하지만)

만약 읽어 들이려는 크기만큼의 노이즈가 앤트로피 풀에 있지 않을 경우 요청한 크기보다 더 적은 값을 읽어 올 수도 있으므로 짧은 시간에 여러개의 랜덤값을 생성해야 할 경우 주석 1.에서 처럼 사이즈를 계산해줘야 할 필요성이 있다.

짧은 시간에 여러개의 랜덤값 생성은 인증값과 같은 중요한 부분에 사용된다고 보기는 힘들다. 이런 경우에는 그냥 random()을 이용하도록 하자.

커널 2.6에서는 /dev/random에 향상이 있다고 하니 한번 확인해 보도록 하자.


--------------------------------------------------------------------------------

2.3.2절. 지원 OS 제한
/dev/random 문자장치를 이용해서 랜덤값을 얻어오는 방법은 매우 효율적이긴 하지만, 모든 OS가 이 문자장치를 지원하는건 아니다. 필자가 아는 바로는 Linux 의 경우 2.x 이상의 커널에서 지원하며 Sun os 의 경우 5.8 이상에서만 지원하는 걸로 알고 있다. Sun os 5.8 의경우에는 패치를 통해서 지원한다.

그럼으로 /dev/random 을 이용한 어플리케이션을 제작하고자 할때는 배포하는 OS에 대해서 신경을 써줘야 한다.


--------------------------------------------------------------------------------

2.3.2.1절. Sun OS 에서의 /dev/random 생성
Sun의 경우 /dev/random 을 생성하기 위한 간단한 방법이 있다. egd 라는 Perl 모듈을 이용하는 방법인데, 방법인데 sunfreeware 나 cpan.org 에서 얻을수 있다. 개인적으로 sunfreeware 에서 버젼에 맞는 egd 를 설치하는걸 추천한다.

sunfreeware 에 가보면 각 버젼별로 egd 모듈이 존재할것이다. 적당한 egd 를 다운받아서 설치하면 되는데, 패키지가 아닌 쏘쓰를 다운받아서 직접 설치하도록 한다. egd-0.x.tar.gz 를 다운받아서 압축을 푼다음에 다음과 같은 방식으로 컴파일후 설치하도록 한다.

[root@localhost egd]# perl Makefile.PL
...
[root@localhost egd]# make
...
[root@localhost egd]# make install
...

성공적으로 컴파일을 마쳤다면 egd.pl 이라는 펄 스크립트가 만들어지고 이걸 이용해서 /dev/random 을 생성할수 있다. [root@locaohost egd]# egd.pl /dev/random
...

egd.pl 을 실행시키면 /dev/random 이 만들어지는데 ls 로 확인해 보면 문자장치 파일이 아닌 Unix Domain 소켓파일임을 알수 있다. 그럼으로 우리가 랜덤값을 얻어오기 위해서는 직접 소켓에 연결해서 /dev/random 소켓파일로 부터 값을 얻어와야 한다. egd.pl 은 perl 로된 Unix Domain 소켓 서버이다.

다음과 같은 방법을 통해서 랜덤값을 얻어올수 있다.


#include
#include
#include
#include
#include
#include
#include
#include
#include

int main()
{
int sockfd;
int clilen;
int value;
char de[36];
struct sockaddr_un clientaddr;

sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("exit : ");
exit(0);
}

clientaddr.sun_family = AF_UNIX;
strcpy(clientaddr.sun_path, "/dev/random");
clilen = sizeof(clientaddr);
if (connect(sockfd, (struct sockaddr *)&clientaddr, clilen) < 0)
{
perror("connect error : ");
exit(0);
}
printf("OK READ\n");

while(1)
{
memset(de, 0x01, 4);
write(sockfd, de, 4);
read(sockfd, (void *)&value, sizeof(int));
printf("%d\n", value);
sleep(1);
}

close(sockfd);
exit(0);
}



참고로 edg.pl 은 SHA(Secure Hash algorithm)을 사용한다. MD5 계열의 Hash 함수와 매우 유사하게 작동하며, SHS(secure hash standard) 에 정의되어 있다. MD5 보다 다소 느리지만 더 안전하다는 평가를 받고 있다.


--------------------------------------------------------------------------------

3절. 결론
이상간단하게 랜덤값을 얻어오는 2가지 일반적인 방법에 대해서 알아보았다. /dev/random 의 경우 나중에 다루게될 ssl 프로그래밍에서도 쓰임으로 알아 놓으면 언젠가 유용하게 써먹을수 있을것이다.



Add New Comment
You are commenting as a Guest. You may select one to log into:

Logged in as Logout from DISQUS

Openssl 을 통한 파일 암호화






Openssl 을 통한 파일 암호화
윤 상배
dreamyun@yahoo.co.kr



--------------------------------------------------------------------------------

차례
1절. 소개
2절. OpenSSL
2.1절. SSL 에 대해서
3절. Openssl 을 통한 암호화
3.1절. blowfish 알고리즘을 이용한 데이타 암호화
3.2절. 데모프로그램 설계문서
3.2.1절. 프로그램 기능설명
3.2.2절. Key 생성하기
3.3절. 코딩
3.3.1절. 동적라이브러리 제작을 위한 API 인터페이스
3.3.2절. fcrypt.c
3.3.3절. blowfish.c
3.4절. 테스트
4절. 결론

--------------------------------------------------------------------------------

1절. 소개
최근에는 단지 방화벽, IDS 로 침입차단/침입탐지 정도의 수준만이 아닌 중요한 데이타의 암호화까지를 이용해서 보안수준을 높이고자 노력하고 있다. 이글은 데이타와 암호화와 관련된 많은 방법중 쉽게 사용할수 있을만한 방법을 제안한다.


--------------------------------------------------------------------------------

2절. OpenSSL
우리는 데이타 암호화를 위해서 가장 널리사용된다고 믿어지고 있는 SSL의 Open 버젼인 OpenSSL 라이브러리를 이용할것이다. 이 라이브러리는 Apache, PGP, SMTP 서버, POP 서버 등 데이타를 교환하는 많은 인터넷 서비스 프로그램에서 사용되어지고 있으며, 로컬데이타(파일) 암호화를 위한 도구로도 널리 사용되고 있다.

OpenSSL 을 사용하는 가장유명한 어플리케이션은 SSH 와 https 를 지원하는 웹브라우저와 웹서버가 될것이다(Opera, Mozilla, explore, lynx 등 대부분의 웹브라우저와 Apache, IIS 웹서버).

이번장에서는 SSL 에 대한 기본적인 개념과 사용방법에 대해서 알아볼것이다.


--------------------------------------------------------------------------------

2.1절. SSL 에 대해서
SSL 은 Secure Sockets Layer 의 줄임말이며, 네트웍 소켓의 데이타 입출력을 암호화 하기 위한 (프로그램)계층(Layer) 이다. SSL 은 넷스케이프 브라우저로 유명한 Netscape 회사에서 만들었으며 산업표준으로 사용되어 지고 있다.

SSL 을 만든이유는 기존의 TCP/IP 가 데이타암호화를 위한 어떠한 방법도 제공해주지 않기 때문으로, 네트웍내에서의 메시지 전송을 안전하게 하기 위한 용도로 만들어졌다. SSL TCP/IP 가 제공하지 않는 데이타 암호화를 지원하기 위해서 다음과 같이 Socket Layer 와 NetWork 계층사에 놓이며, Socket Layer 를 통해 전달된 데이타를 암호화 해서 NetWork 계층에 넘겨주며, Network 계층을 통해서 전달된 암호화 데이타를 복호화 해서 Socket Layer 로 전달해주는 구조를 가진다.

그림 1. SSL 계층구조




SSL 은 전자서명에 널리사용되는 암호화 알고리즘인 RSA 를 주 알고리즘으로 사용한다. RSA 는 암호화와 복호화를 위해서 개인키 와 공개키 를 사용한다. 간단히 말해서 암호화에 사용하는 키와 복호화에 사용되는 키가 분리되어 있는 알고리즘이다.

개인키는 말그대로 개인이 유일하게 가지고 있는키이며 이 키를 이용해서 데이타를 암호화 시킨다. 암호화된 데이타를 복호화 하기 위해서는 공개키를 사용해야 하는데, 이 공개키는 모두에게(데이타를 보내고자하는) 공개된다.


1.데이타를 수신 하는 사람에게 공개키를 보낸다.

2.보내고자 하는데이타를 개인키를 이용해서 암호화 한다.

3.암호화된 데이타를 수신자에게 전송한다. 암호화되어 있음으로 다른 사람은 볼수 없다.

4.데이타 수신자는 공개키를 이용해서 데이타를 복호화 한다.

메일의 암호화를 위해서 PGP 를 사용해본적이 있다면, 위의 과정을 쉽게 이해할수 있을것이다. PGP 를 사용할경우 데이타 송신자는 자신 데이타 암호화를 위한 개인키와 복호화를 위한 공개키를 생성하고 만들어진 공개키를 요러가지 경로를 통해서 데이타 수신자에게 전달한다(디스크, 혹은 메일, 웹등으로). 그후 데이타를 보낼때는 개인키로 데이타를 암호화 해서 보내고, 받은 측은 공개키를 이용해서 데이타를 복호화 한다.

이상 간단하게 SSL 에 대해서 알아보았는데, SSL 은 매우 방대하며 복잡한 내용임으로 여기에서 모두 다룰수는 없다. 여기에 관한 내용은 openssl.org를 참고하기 바란다.


--------------------------------------------------------------------------------

3절. Openssl 을 통한 암호화
3.1절. blowfish 알고리즘을 이용한 데이타 암호화
이 문서에서는 RSA 암호화 알고리즘대신 blowfish 알고리즘을 사용해서 데이타를 암/복호화 할것이다. Blowfish 는 데이타 암호화및 복호화를 위해서 동일한 키를 사용하는 대칭 알고리즘(symmetric algorithm)을 사용하고 있다.

암/복호화를 위한 다른키를 사용하는 RSA 알고리즘에 비해서 사용하기가 간단하고, 더 빠른 수행능력을 보여준다는 장점을 가진다.


--------------------------------------------------------------------------------

3.2절. 데모프로그램 설계문서
우리는 파일을 암호화해서 저장하는 어플리케이션을 제작할 것이다. 코딩에 들어가기 전에 간단단하게 기능과 원리를 정의하는 설계도(설계도라고 하기엔 부족하지만)를 만들어보도록 하겠다.


--------------------------------------------------------------------------------

3.2.1절. 프로그램 기능설명

■프로그램은 key 를 생성하고 생성된 key 를 이용해서 프로그램 인자로 주어지는 파일을 암호화 하게 된다. 암호화된 파일을 복호화 하기 위해서는 key 가 필요하게 되는데, 이 key 는 별도의 파일로 저장하게 된다. 그럼으로 암호화된 파일을 복호화 하기 위해서는 반드시 key 파일이 존재해야만 한다.

■암호화된 파일의 가장 앞부분에는 암호화된 파일의 정보를 알려주는 헤더가 들어갈것이다. 이것은 이 파일이 우리가 만든 프로그램을 통해서 암호화 되었다는 정보와 함께 버젼정보, 사용한 암호화 알고리즘 종류, key 를 제작한사람 등에 관한 정보가 들어간다.

■파일에 이처럼 다양한 부가정보를 추가하는 이유는 확장이 필요한 경우가 생길수 있기 때문이다. 예를들어 지금은 단지 blowfish 알고리즘만을 사용하고 있지만 다른 암호화알고리즘을 지원하도록 추가할수도 있기 때문이다.

■또한 어플리케이션은 동적라이브러리를 지원하도록 만들어진다. 이유는 다른 암호화 알고리즘이 추가되었을경우 코드를 다시 컴파일하는 불편함을 줄이기 위해서이다.


--------------------------------------------------------------------------------

3.2.2절. Key 생성하기
fcrypt 에서는 암/복호화를 위한 키의 크기를 128bit 로 하겠다. 만들어진 키를 이용해서 암호화를 한후 별도의 파일로 저장될것이다. 이유는 나중에 복호화 할때도 동일한 키를 사용해야 하기 때문이다. 복호화 할때는 존재하고 있는 키 파일에서 파일에 대한 키를 얻어오고 이 데이타를 이용해서 복호화 할것이다.

키의 생성은 커널난수 생성기인 /dev/random 문자장치파일을 이용할것이다.


--------------------------------------------------------------------------------

3.3절. 코딩
프로그램의 이름은 fcrypt 로 할것이다. 코드는 크개 2개의 부분으로 이루어지는데, 하나는 fcrypt.c 로써 실제 사용자에게 제공되는 프로그램이며, 다른 하나는 bolwfish.c 로써 암호화기능을 제공하는 프로그램이다.

blowfish.c 는 main 함수를 제공하지 않으며, 동적라이브러리형태로 제작될 것이며, fcrypt 에서는 이 라이브러리를 적재해서 파일을 암호화 하게 될것이다.


--------------------------------------------------------------------------------

3.3.1절. 동적라이브러리 제작을 위한 API 인터페이스
동적라이브러리 형태로 만드는 이유는 사용자이 필요에 따라서 다양한 암호화 알고리즘을 적용시킬수 있도록 유연성을 높이기 위함이다. 일종의 Plug in 형태로 작동하게 된다.

이처럼 본체프로그램과 기능추가를 위한 Plug in 이 따로 분리 되어서 제작될경우에 Plug in 의 제작을 위한 공통 인터페이스를 제공하게 된다. 그래야만 본체프로그램 코드의 수정없이 단지 plug in 라이브러리만 추가함으로써, 기능을 확장시킬수 있기 때문이며, 그러기 위해서는 공통된 인터페이스에 따라서 Plug in 라이브러리를 제작해야 한다.

동적라이브러리는 반드시 아래와 같은 4가지의 인터페이스를 (정확히 말하자면 함수선언)을 이용해서 프로그래밍 되어야한다. 본체 프로그램은 단지 이 4가지 함수들만을 호출해서 모든 작업을 할수 있도록 라이브러리 제작자는 신경을 써야한다.

int crypt_open();
int mycrypt(char *filename, cryptinfo header);
int mydecrypt(char *filename, cryptinfo header, char *keyfile);
int crypt_close();

crypt_open()과 crypt_close() 는 라이브러리 개발자의 편의를 위해서 제공되는 인터페이스다. 예를들자면 특정 암호화알고리즘을 적용해서 라이브러리를 만들경우 여러가지 초기화작업이 필요한 경우가 있을것이다. 메모리 할당, 초기화, 메모리 해제 등과 같은 작업이 대표적일 것이다. crypt_open() 은 이러한 초기화 작업을 위해서 사용되며, crypt_close() 는 메모리 해제와 같은 작업을 위해서 사용된다.

mycrypt() 는 암호화를 위한 함수이다. filename 은 암호화 하고자 하는 파일이름이며 header는 헤더정보를 입력받기 위해서 사용되는 아규먼트이다.

mydecrypt() 는 복호화를 위한 함수이다. filename 은 복호화 하고자 하는 파일이름 이며, header 는 헤더정보, keyfile 은 복호화 하기 위해서 사용하는 키가 저장된 파일의 이름이다. header 이 인자로 넘겨지는 이유는 복호화 하고자 하는 파일의 버젼체크등을 위한 목적이다. 암호파일의 버젼과 본체프로그램의 버젼이 전혀 틀릴경우 (메이져 버젼에서 차이가 생길경우) 복호화가 제대로 되지 않을수 있음으로 이를 체크하기 위한 목적이다.

그럼으로 main() 함수를 포함하는 본체 프로그램은 단지 다음과 같이 함수를 호출함으로써 모든 작업을 마치게 되며, 암호/복호화 알고리즘 내부에서 어떤 일을 하는지 신경쓰지 않아도 된다. 일종의 캡슐화라고 볼수 있다.

main()
{
아규먼트 처리..
...
아규먼트를 분석해서 어떤 알고리즘을 원하는지 확인한다.
plugin 설정파일에서 해당 알고리즘을 위한 라이브러리 이름을 얻어온다.
라이브러리를 로드 해서 라이브러리에 대한 handler 를 만든다.
crypt_open() 를 호출한다.
만약 암호화일경우 mycrypt() 를 호출한다.
복호화일경우 mydecrypt() 를 호출한다.
crypt_close()를 호출한다.
로드한 라이브러리를 close 한다.
...
}




--------------------------------------------------------------------------------

3.3.2절. fcrypt.c
이 코드는 아규먼트로 암/복호화 할 파일의 이름을 받아들인다. 암호화 할것인지 복호화 할것인지는 역시 아규먼트를 통해서 지정할수 있도록 할것이다. 역시 아규먼트를 통하여서 암호화방법을 지정할수 있으며, 아규먼트로 지정된 암호화 방법에 따라서 어플리케이션은 적당한 라이브러리를 동적으로 적재해서 암호화 혹은 복호화 하게 된다.

이 프로그램은 암호화종류에 따른 라이브러리를 동적으로 적재하기 위해서 설정파일을 가지며, 아규먼트를 통해서 선택된 암호화방법에 적당한 라이브러리를 찾기 위한 목적으로 사용된다. 설정파일은 다음과 같이 구성된다. 설정파일의 이름은 plugin.cfg 로 하겠다.

blowfish,libblowfish,blowfish 알고리즘
mycrypt,libmycrypt,사용자 정의 암호화 알고리즘
null,libnull,null function 알고리즘

3개의 필드를 가진다. 첫번째 필드에는 암호화이름 두번째 필드는 불러들일 라이브러리 이름, 세번째 필드는 간단한 설명이다.

암호화할때는 파일의 가장앞에 암호화된 파일의 정보구조체를 집어 넣을것이다. 이 구조체는 다음과 같다.

struct cryptinfo
{
char progname[8]; // 프로그램이름
short int maversion; // 메이저 버젼
short int miversion; // 마이너 버젼
char crypttype[80]; // 암호화타입
char maker[20]; // 만든사람정보
};



다음은 실제코드이며, 설명은 주석으로 대신하도록 한다.

예제 : fcrypt.c

#include

#include
#include
#include
#include
#include
#include
#include

#define CRYPT 1
#define DECRYPT 2
// 프로그램의 메이저 버젼
#define MAVERSION 1
// 프로그램의 마이너 버젼 즉 1.o
#define MIVERSION 0
// 프로그램 이름
#define PROGNAME "fcrypt"
// 읽어들일 플러그인 파일
#define CFGFILE "plugin.cfg"

// 암호화된 파일의 가장 앞부분에 저장될
// 헤더정보 파일
typedef struct _cryptinfo
{
// 프로그램 이름
char progname[8];
// 메이저 버젼
short int maversion;
// 마이너 버젼
short int miversion;
// 암호화 타입
char crypttype[12];
// 암호화한 사람
char maker[20];
} cryptinfo;

// 도움말
void help();
// plug in 파일이 있는지 검사
int checkcfgfile();
// 라이브러리를 동적으로 적재한다.
void *loadlibrary(char *);
// plbu in 파일에서 로드시켜야될
// 라이브러리 이름을 가져온다.
int getlibname(char *,char *);
// 동적으로 적재된 라이브러리를 해제한다.
void closelibrary(void *handle);
// 플로그인 목록을 보여준다.
int showpluginlist();
// 암호화될 파일에 들어갈 헤더를 만든다.
cryptinfo makeheader(char*, char*);

int main(int argc, char **argv)
{
int opt;
// 암호화인지 복호화인지 판단.
int iscrypt;
// 플러그인 파일이름
char cfgfile[80];
// 암호화시킬 파일이름
char filename[80];
// 암호화 종료
char crypttype[80];
int filenum;
char buf[256];
// 키파일 이름
char keyfile[80];
FILE *fp;

// 동적라이브러리 핸들러
void *handle;

// 핸들러를 통해서 읽어들일 함수포인터들
int (*crypt_open)();
int (*crypt_close)();
int (*encrypt)(char *, cryptinfo);
int (*decrypt)(char *, cryptinfo, char *);

int isfileinfo = 0;
char *error;
memset(keyfile, 0x00, 80);

// 아규먼트를 읽어들여서
// 값을 세팅한다.
while((opt = getopt(argc, argv, "lit:hcdk:")) != -1)
{
switch(opt)
{
// 플러그인 리스트를 보여준다.
case 'l':
showpluginlist();
return 1;
break;
// 암호화된 파일정보를 보여줄것인지..
case 'i':
isfileinfo = 1;
break;
// 암호화 방법 가져오기
case 't':
strncpy(crypttype, optarg,80);
break;
// 도움말 보여주기
case 'h':
help();
return 0;
break;
// 암호화 할때
case 'c':
iscrypt = CRYPT;
break;
// 복호화 할때
case 'd':
iscrypt = DECRYPT;
break;
// 키파일 이름 얻어온다.
case 'k':
strncpy(keyfile, optarg, 80);
break;
// 암호화 시킬 파일
default:
printf("파일 : %s\n", optarg);
break;
}
}

// 복호화를 위해서는 반드시 key 파일이
// 지정되어 있어야 한다.
if (iscrypt == DECRYPT && strlen(keyfile) == 0)
{
printf("KEY file can not find\n");
help();
return 1;
}

// 압축해야될 파일이름을 얻어온다.
// 반드시 하나의 파일이름이어여 한다.
// 파일 목록을 지원하도록 하도 싶다면
// 각자 수정해 보기 바란다.
filenum = argc - optind;
if (filenum != 1)
{
printf("only one file\n");
return 1;
}
strncpy(filename, argv[optind],80);

// 암호화된 파일의 정보를 얻어온다.
if (isfileinfo)
{
fileinfo(filename);
return 0;
}

// -----------------------------------------------
// 실제 암호화 루틴
// 암호화혹은 복호화할 파일을 열고
// 암호화타입에 의하여 해당 라이브러리를 링크해서
// iscrypt 값에 따라서 1이면 암호화 0이면 복호화
// 한다.
// -----------------------------------------------
handle = loadlibrary(crypttype);
if (handle == NULL)
{
fprintf(stderr, "library load error\n");
exit(0);
}

crypt_open = dlsym(handle, "crypt_open");
if ((error = dlerror()) != NULL)
{
fprintf(stderr, "%s\n", error);
exit(0);
}

if (crypt_open() == -1)
{
fprintf(stderr, "crypt_open error\n");
exit(0);
}

// 만약 암호화 이라면
if (iscrypt == CRYPT)
{
encrypt = dlsym(handle, "mycrypt");
if ((error = dlerror()) != NULL)
{
fprintf(stderr, "%s\n", error);
exit(0);
}
// makeheader 를 이용해서 암호화된 파일에 들어갈
// 헤더를 만든다.
if (encrypt(filename,makeheader("yundream", crypttype)) == -1)
{
printf("encrypt error\n");
exit(0);
}

printf("ok encrypt\n");
}
// 만약 복호화 이라면
else if (iscrypt == DECRYPT)
{
decrypt = dlsym(handle, "mydecrypt");
if ((error = dlerror()) != NULL)
{
fprintf(stderr, "%s\n", error);
exit(0);
}
if(decrypt(filename,makeheader("yundream",crypttype), keyfile) == -1)
{
printf("decrypt error\n");
exit(0);
}

}

crypt_close = dlsym(handle, "crypt_close");
if ((error = dlerror()) != NULL)
{
fprintf(stderr, "%s\n", error);
exit(0);
}
crypt_close();


// 동적라이브러리 핸들러 닫기
closelibrary(handle);
return 1;
}

// 도움말
void help()
{
printf("Usage\n");
printf("encrypt : fcrypt -c crypt.c -t blowfish\n");
printf("decrypt : fcrypt -d crypt.c.cry -t blowfish -k crypt.c.key\n");
printf("info : fcrypt -i crypt.c.cry\n");
printf("plugin list : fcrypt -l\n");
}

// plugin 파일을 확인한다.
int checkcfgfile()
{
if (access(CFGFILE, F_OK) == 0)
{
return 1;
}
return -1;
}

// 플러그인 파일로 부터
// 로드해야할 라이브러리이름을 얻어온다.
// 리턴값으로 동적라이브러리에 대한 핸들값을
// 되돌려준다.
void *loadlibrary(char *crypttype)
{
void *handle;
char *libname;
libname = (char *)malloc(80);
if (getlibname(libname, crypttype) == -1)
{
free(libname);
return NULL;
}

handle = dlopen(libname, RTLD_NOW);
if (!handle)
{
printf("%s\n", dlerror());
return NULL;
}
free(libname);
return handle;
}

// 플러그인 설정파일을 읽어들이고
// 파싱해서 해당 라이브러리 이름을 돌려준다.
int getlibname(char *libname, char *acrypttype)
{
FILE *fp;
char buf[80];

char null[2];
char crypttype[80];
char uselib[80];
char desc[80];
fp = fopen(CFGFILE, "r");
if (fp == NULL)
{
printf("cannot open cfg file : %s\n", CFGFILE);
return -1;
}
while(fgets(buf, 80, fp)!=NULL)
{
sscanf(buf, "%[^,]%[,]%[^,]%[,]%[^\n]",
crypttype, null, uselib, null, desc);
if (strcmp(crypttype,acrypttype) == 0)
{
snprintf(libname, 80, "plugin/%s", uselib);
return 1;
}
}
fclose(fp);
printf("cannot used crypt type %s\n", acrypttype);
return -1;
}

// 동적 라이브러리 핸들러를 닫는다.
void closelibrary(void *handle)
{
dlclose(handle);
}

// 헤더정보를 만든다.
cryptinfo makeheader(char *maker, char *crypttype)
{
cryptinfo header;
memset((void *)&header, 0x00, sizeof(header));
strncpy(header.progname, PROGNAME, 8);
strncpy(header.crypttype, crypttype, 12);
strncpy(header.maker, maker, 20);

header.maversion = MAVERSION;
header.miversion = MIVERSION;

return header;
}

// 암호화된 파일에 대한 정보를 얻어온다.
// 정보는 헤더파일을 분석해서 가져온다.
int fileinfo(char *filename)
{
int fd;
int n;
cryptinfo header;
if ((fd = open(filename, O_RDONLY)) < 0)
{
fprintf(stderr, "%s not found\n", filename);
return -1;
}
n = read(fd, (void *)&header,sizeof(header));
// fcrypt 에서 지원하는 암호화된 파일인지를
// 확인한다.
if (n < sizeof(header) || (strcmp(header.progname, PROGNAME) != 0))
{
fprintf(stderr, "Unknown file format\n");
return -1;
}
printf("Version : %d.%d\n", header.maversion, header.miversion);
printf("crypttype : %s\n", header.crypttype);
close(fd);
return 1;
}

// 플러그인 설정파일을 분석해서
// 지원되는 플러그인 목록을 보여준다.
int showpluginlist()
{
FILE *fp;
char buf[80];

char null[2];
char crypttype[80];
char uselib[80];
char desc[80];

fp = fopen(CFGFILE,"r");
if (fp == NULL)
{
printf("Cannot open cfgfile : %s\n", CFGFILE);
return -1;
}

while(fgets(buf, 80, fp)!=NULL)
{
sscanf(buf, "%[^,]%[,]%[^,]%[,]%[^\n]",
crypttype, null, uselib, null, desc);
printf("%-20s%-20s%-20s\n", crypttype, uselib, desc);
}
fclose(fp);
return 1;
}



fcrypt.c 는 다음과 같이 컴파일하면 된다.

[root@localhost crypt2]# gcc -o fcrypt fcrypt.c -ldl -lcrypto




--------------------------------------------------------------------------------

3.3.3절. blowfish.c
이것은 fcrypt 프로그램에서 동적으로 적재할 플러그인 프로그램으로 blowfish 알고리즘을 통해서 주어진 파일을 암호화 하고 복호화 한다.

blowfish 는 앞에서 말했듯이 암호화에 사용된 key 를 가지고 복호화를 하게 된다. 당연히 이러한 key 데이타를 파일로 저장하고 있어야 할것이다.

그럼으로 blowfish 는 파일을 암호화 하게 될경우 암호화된 파일과 암호화에 사용된 key 를 저장하는 파일 이렇게 2개의 파일을 생성한다. 암호화된 파일은 원본파일뒤에 ".cry" 를 붙이고, key 파일은 원본파일 뒤에 ".key"를 붙이도록 해서 생성하도록 할것이다.

key 사이즈는 128 bit 로 할것이다. 이 blowfish 는 openssl 의 crypto 라이브러리에서 제공한다. 그리고 랜덤한 key 를 생성하기 위해서 /dev/random 커널 난수 생성기를 이용할것이다. 커널 난수 생성기에 대해서는 random 값 얻어오기를 참고하기 바란다.

crypto 에서 제공하는 blofish 암호화 알고리즘에는 key 외에도 initalization vector 가 필요하다. 이것의 크기는 64bit 로 할것이며, 마찬가지로 /dev/random 을 이용해서 랜덤하게 생걱할것이다. 고로 실제 key 파일에는 key 값과 initalization vector 값이 저장될 것이다.

그리고 Cipher Block Chaining(CBC) 모드 상태에서 암/복호화를 하게 될것이다.

예제 : blowfish.c

#include
#include
#include
#include
#include
#include
#include

#define IP_SIZE 256
#define OP_SIZE (256+EVP_MAX_BLOCK_LENGTH)

// 128bit key 와
// 64bit initalization vector 를
// 저장하기 위한 구조체
typedef struct _keybuf
{
char key[16];
char vec[8];
} keybuf;

// 암호화된 파일에 들어갈 헤더 정보
typedef struct _cryptinfo
{
char progname[8];
short int maversion;
short int miversion;
char crypttype[12];
char maker[20];
} cryptinfo;
keybuf mykey;


// 인터페이스를 통일시키기 위한
// 공통 함수
// 필요에 따라서 각종초기화및 메모리 할당
// 관련 코드를 넣을수 있다.
int crypt_open()
{
return 1;
}

// /dev/random 을 이용해서
// key 와 initalization vector 에 들어갈 값을
// 생성한다.
int keygen()
{
int fd;
if ((fd = open("/dev/random", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}

if ((read(fd, mykey.key, 16)) == -1)
{
perror("read key error");
return -1;
}

if ((read(fd, mykey.vec, 8)) == -1)
{
perror("read key error");
return -1;
}
close (fd);
return 1;
}


// 복호화 함수
// filename 을 암호화하며,
// header 를 암호화된 파일 가장앞에 write 한다.
// 또한 복호화에 사용될 keyfile 을 지정해준다.
int mydecrypt(char *filename, cryptinfo header, char *keyfile)
{
int fd;
char inbuf[IP_SIZE];
char outbuf[OP_SIZE];
int n, olen;
int ecode;
cryptinfo localheader;
EVP_CIPHER_CTX ctx;

getkeyvalue(keyfile);
EVP_CIPHER_CTX_init (&ctx);
EVP_DecryptInit(&ctx, EVP_bf_cbc(), mykey.key, mykey.vec);

// 암호화된 파일을 오픈한다.
if ((fd = open(filename, O_RDONLY)) == -1)
{
perror("debug error");
return -1;
}

// 헤더정보를 읽어들인다.
if (read(fd, (void *)&localheader, sizeof(cryptinfo)) == -1)
{
perror("Header read error\n");
return -1;
}
// 헤더정보를 토대로 버젼체크와
// 암호화 종류를 체크한다.
if ( (ecode = checkcryptfile(header, localheader)) == -1)
{
return ecode;
}

// 파일 내용을 읽어들여서
// 복호화 한다.
while(1)
{
memset(inbuf, 0x00, IP_SIZE);
if ((n = read(fd, inbuf, IP_SIZE)) == -1)
{
perror("read error");
return -1;
}
else if (n == 0)
{
break;
}

memset(outbuf, 0x00, OP_SIZE);
if (EVP_DecryptUpdate(&ctx, outbuf, &olen, inbuf, n) != 1)
{
printf("error in decrypt\n");
return -1;
}

// 복호화한 내용은 화면 출력시킨다.
write(1, outbuf, olen);
}

if (EVP_DecryptFinal(&ctx, outbuf, &olen) != 1)
{
printf("error in decrypt final\n");
return -1;
}
write(1, outbuf, olen);

close(fd);
EVP_CIPHER_CTX_cleanup (& ctx);
}

// key 파일을 만든다.
int makekeyfile(char *filename)
{
int fd;

if((fd = open(filename, O_WRONLY|O_CREAT)) == -1)
{
return -1;
}
write(fd, (void *)&mykey, sizeof(mykey));
close(fd);
}

// key 파일로 부터
// key 정보를 얻어온다.
int getkeyvalue(char *filename)
{
int fd;
if ((fd = open(filename, O_RDONLY)) == -1)
{
return -1;
}
read (fd, (void *)&mykey, sizeof(mykey));
close(fd);
}

// 암호화에 사용되는 함수
// filename 파일을 암호화 한다.
// 암호화된 파일의 가장 앞에는 header 정보를 붙인다.
int mycrypt(char *filename, cryptinfo header)
{
char inbuf[IP_SIZE];
char outbuf[OP_SIZE];
int olen, n;
int outfd;
int fd;
char defile[80];
char keyfile[80];
EVP_CIPHER_CTX ctx;

// 암호화파일이름
snprintf(defile, 80, "%s.cry", filename);
// key 파일 이름
snprintf(keyfile, 80, "%s.key", filename);
keygen();

// key 파일을 생성한다.
makekeyfile(keyfile);


EVP_CIPHER_CTX_init (&ctx);
EVP_EncryptInit(&ctx,EVP_bf_cbc(), mykey.key, mykey.vec);

// 암호화된 내용이 저장될 파일을 연다.
if ((outfd = open(defile, O_WRONLY|O_CREAT)) == -1)
{
return -1;
}

// 암호화할 원본 파일을 연다.
if ((fd = open(filename, O_RDONLY|O_CREAT)) == -1)
{
return -1;
}

// 암호화 파일의 가장 앞부분에 헤더정보를 넣는다.
if (write(outfd, (void *)&header, sizeof(header)) == -1)
{
perror("header write error");
return -1;
}

// 원본파일로 부터 내용을 읽어서
// 암호화하고 이것을 파일로 저장한다.
while(1)
{
memset(inbuf, 0x00, IP_SIZE);
if ((n = read(fd, inbuf, IP_SIZE)) == -1)
{
perror("read error");
return -1;
}
else if (n == 0)
break;

if (EVP_EncryptUpdate(&ctx, outbuf, &olen, inbuf, n) !=1 )
{
printf("error in encrypt update\n");
return -1;
}
if ((n = write(outfd, outbuf, olen)) == -1)
{
perror("write error");
return -1;
}
}

if (EVP_EncryptFinal(&ctx, outbuf, &olen) != 1)
{
printf("error in encrypt final\n");
return -1;
}
if ((n = write(outfd, outbuf, olen)) == -1)
{
perror("write error");
return -1;
}

close(outfd);
close(fd);
EVP_CIPHER_CTX_cleanup (&ctx);
return 1;
}

// 암호화된 파일의 헤더내용을 이용해서
// 버젼 체크와 암호화방법에 대한 체크를 한다.
int checkcryptfile(cryptinfo h1, cryptinfo h2)
{
if (h1.maversion != h2.maversion)
{
fprintf(stderr, "wrong version\n");
return -1;
}
if (strcmp(h1.crypttype, h2.crypttype) != 0)
{
fprintf(stderr, "wrong encrypt type\n");
return -1;
}

return 1;
}

int crypt_close()
{
return 1;
}



blowfish.c 는 공유라이브러리 형태로 만들어줘야 한다. 다음과 같이 공유라이브러리 형태로 만들어주자.

[root@localhost crypt2]# gcc -c -fPIC blowfish.c
[root@localhost crypt2]# gcc -shared -W1,-soname,libblowfish.so.1 -o libblowfish.so.1.0.1 blowfish.o

이렇게 하면 libblowfish.so.1.0.1 이라는 라이브러 파일이 만들어 진다. 이 파일은 plugin 디렉토리를 만들어서 복사하고 다음과 같이 심볼릭 링크를 걸어주도록 하자. [root@localhost crypt2]# mkdir plugin
[root@localhost crypt2]# cp libblowfish.so.1.0.1 plugin/
[root@localhost crypt2]# cd plugin
[root@localhost plugin]# ln -s libblowfish.so.1.0.1 libblowfish.so




--------------------------------------------------------------------------------

3.4절. 테스트
테스트를 하기 위해서는 plugin.cfg 파일이 있어야 하며, plugin 디렉토리 밑에 해당 암호화 알고리즘을 지원하는 라이브러리가 존재하고 있어야 한다. 다음은 필자의 plugin.cfg 의 파일이다.

blowfish,libblowfish.so,blowfish 알고리즘
null,libnull.so,null function 알고리즘

blowfish 는 무엇인지 알테고, null 은 필자가 동적라이브러리 테스트용으로 만든 것으로 말그대로 null 이다. 즉 아무런 암호화 과정없이 그대로 원본내용을 그대로해서 암호화파일을 만든다. 각자 만들어 보기 바란다.

plugin 밑에는 다음과 같은 파일들이 있다.

[root@localhost plugin]# ls
libblowfish.so libblowfish.so.1.0.1 libnull.so libnull.so.1.0.1



이제 테스트를 해보자, 테스트 결과의 확인을 쉽게 하기 위해서 text 파일을 테스트에 사용하도록 하자. 필자는 fcrypt.c 를 암호화/복화화 테스트에 사용하도록 했다.

다음은 암호화 테스트로 fcrypt.c 를 blowfish 알고리즘을 사용한 테스트 결과이다.

[root@localhost crypt2]# ./fcrypt -c fcrypt.c -t blowfish
ok encrypt

실행결과 암호화된 파일인 fcrypt.c.cry 와 key 가 저장된 fcrypt.c.key 가 생성되었음을 확인할수 있을것이다.

다음은 암호화된 파일의 정보를 얻어오는 테스트이다.

[root@localhost crypt2]# ./fcrypt -i fcrypt.c.cry
Version : 1.0
crypttype : blowfish

다음은 fcrypt 가 현재 지원하는 plug in 알고리즘의 목록을 출력한 것이다. [root@localhost crypt2]# ./fcrypt -l
blowfish libblowfish.so blowfish 알고리즘
null libnull.so null function 알고리즘

다음은 복호화 시킨 결과이다. [root@localhost crypt2]# ./fcrypt -d fcrypt.c.cry -t blowfish -k fcrypt.c.key
#include

#include
#include
#include
#include
#include
#include
#include

#define CRYPT 1
#define DECRYPT 2
// 프로그램의 메이저 버젼
....




--------------------------------------------------------------------------------

4절. 결론
이상 openssl 을 이용한 암호화방법에 대해서 알아보았다. 위의 예제 프로그램들은 수정해야할 여지가 많을것이다.

여기에서는 단지 파일만을 예로 들었지만, 네트웍 프로그래밍등에도 충분히 응용이 가능할것이다.

DIGITAL 365℃ :: '쇼옴니아냐, 아이폰이냐'...KT의 딜레마

DIGITAL 365℃ :: '쇼옴니아냐, 아이폰이냐'...KT의 딜레마

KT가 '걸작 중의 걸작'이라고 자평하고 있는 '쇼옴니아(SPH-8400)' 출시가 임박해오면서 업계에 비상한 관심이 쏠리고 있다. 쇼옴니아가 '제2의 아이폰'이 될지, 아니면 '아이폰 킬러'가 될지 엇갈린 관측이 제기되고 있다.

10일 관련 업계에 따르면, KT는 세계 최초로 3세대(3G) WCDMA와 와이브로, 와이파이(WiFi)를 동시에 제공하는 '쇼옴니아'를 15일경 출시할 방침이다.

삼성전자가 제작하고 마이크로소프트(MS)의 '윈도 모바일' 운영체제(OS)를 탑재한 쇼옴니아는 KT가 개발 단계부터 직접 참여해 사용자환경(UI)에 역점을 두고 역량을 집중했던 유무선융합(FMC)폰이다. KT 관계자는 "KT가 그동안 출시한 단말기와는 비교할 수 없는 기능적 우위에 있다"면서 "걸작 중의 걸작이 될 것"이라고 기대감을 숨기지 않았다.

쇼옴니아는 WCDMA+와이파이+와이브로를 동시에 제공해 와이파이가 안되는 지역에서는 와이브로로 무선 인터넷을 즐길 수 있다. 또한 '스마트폰은 쓰기 어렵다'는 편견을 깨기 위해 사용자 환경을 단순화, 상하좌우 손쉬운 터치동작으로 원하는 메뉴를 빠르게 선택할 수 있도록 구현했다.

KT측은 "이통사, 단말 제조사, 운영체제사의 독자적인 메뉴로 인한 서비스 산재와 중복성의 문제를 탈피하기 위해 KT와 삼성, MS가 제공하는 서비스를 통합해 고객 관점에서 최적화된 메뉴를 제공한다"면서 "아이폰을 능가하는 스마트폰이 될 것으로 확신한다"고 강조했다.
 
쇼옴니아는 요금 체계가 아이폰보다 유리하다는 장점도 있다. 아이폰과 동일한 4가지 스마트폰 요금제를 사용하면서도 각 요금제마다 무선데이터 용량을 50%씩 추가했기 때문이다. 예컨대, 월정액 4만5000원(i라이트)은 750MB(아이폰은 500MB), 6만5000원(i-미디엄)은 1.5GB(아이폰 1GB)에 달한다. 또한 내년 3월까지 와이브로를 무료로 사용할 수 있다.

김우식 KT개인고객부문 사장은 "쇼옴니아에 아이폰과 비슷한 수준의 보조금을 지급할 것"이라며 가격 경쟁력에서도 아이폰에 뒤지지 않을 것임을 시사했다. 특히 KT는 내년 2월 신사옥에 입주할 때 임직원들에게 쇼옴니아를 업무용으로 제공할 방침인 것으로 전해졌다.

KT가 이처럼 '쇼옴니아'를 부각시키는 것은 세계 최초 3W 단말(WCDMA와 와이브로, 와이파이)이라는 것 외에도 '아이폰 = KT'라는 인식에서 벗어나기 위한 시도라는 측면도 있다는 것이 업계의 분석이다. 애플 아이폰은 국내 시판 열흘만에 9만명이 가입하는 등 높은 인기를 구가하고 있다.

업계 관계자는 "아이폰이 세계적인 히트상품이기는 하지만, 개발에 직접 참여한데다 KT가 추구하는 FMC와도 잘 어울리는 쇼옴니아에 힘이 쏠릴 수 밖에 없을 것"이라고 말했다.

한편, LG텔레콤은 본격 출시를 앞두고 있는 '오즈옴니아(SPH-M7350)'의 예약판매를 9일부터 시작했다.

SK텔레콤의 'T옴니아2'에 이어 KT 쇼옴니아, LG텔레콤 오즈옴니아가 출격을 서두르면서 국내 스마트폰 시장에 '옴니아' 바람이 거세게 몰아칠 전망이다. 이통사 관계자는 "이통 3사가 옴니아를 주력 제품으로 내세우면서 아이폰 중심의 시장 판도에도 적잖은 변화가 예상된다"고 전망했다.

2009년 12월 9일 수요일

OpenSSH 설정



소개
This section of the Ubuntu Server Guide introduces a powerful collection of tools for the remote control of networked computers and transfer of data between networked computers, called OpenSSH. You will also learn about some of the configuration settings possible with the OpenSSH server application and how to change them on your Ubuntu system.

OpenSSH는 원겨으로 컴퓨터를 조종하거나 컴퓨터 간의 파일을 전송하기 위한 Secure Shell (SSH) 프토토콜 도구들의 자유롭게 사용할 수 있는 버전 입니다. 이러한 기능을 가지는 전통적인 도구들로는, telnet 이나 rcp 가 있지만, 보안이 적용되지 않고 사용될 때 사용자의 암호를 들여다 볼수 있는 텍스트로 전송 합니다. OpenSSH는, 이러한 전통적인 도구들을 효과적으로 대체하는, 서버 데몬과 보안, 암호화된 원격 조종과 파일 전송 동작 기능을 가지는 클라이언트 도구들을 제공 합니다.

OpenSSH 서버 구성 요소는, sshd 이고, 어떠한 클라이언트 도구이던 클라이언트 접속을 위하여 끊임없이 듣습니다. 접속 요청이 일어났을 때, sshd 는 연결하는 클라이언트 도구의 종류에 따라 올바른 접속을 만듭니다. 예를 들어, 원격 컴퓨터가 ssh 클라이언트 프로그램을 가지고 접속을 한다면, 그 OpenSSH 서버는 인증 후에 원격 조종 세션을 만듭니다. 만약 원격 사용자가 scp 를 가지고 OpenSSH 서버를 연결하면, 그 OpenSSH 서버 데몬은 인증 후에 서버와 클라이언트 간에 안전한 파일 복사를 시작 합니다. OpenSSH는 일반 암호, 공개 키, 그리고 Kerberos 티켓을 포함하는 여러가지 인증 방법을 사용할 수 있습니다.

설치
OpenSSH 클라이언트와 서버 프로그램의 설치는 간단 합니다. 우분투 시스템에 OpenSSH 클라이언트 프로그램을 설치하려면, 다음의 명령을 터미널 프롬프트에서 사용 합니다:


sudo apt-get install openssh-client

OpenSSH 서버 프로그램과 관련되는 지원 파일을 설치하려면, 다음의 명령을 터미널 프롬프트에서 사용 합니다:


sudo apt-get install openssh-server

설정
여러분은 OpenSSH 서버 프로그램, sshd, 의 기본 동작을 /etc/ssh/sshd_config 파일을 편집하는 것으로 설정할 수 있습니다. 이 파일에서 사용되는 설정 지시자에 대한 정보는, 터미널 프롬프트에서 다음의 명령을 입력하여 해당 매뉴얼 페이지를 보십시오:


man sshd_config

sshd 설정 파일에는 통신 설정과 인증 모드 등을 조종하기 위한 많은 지시자들이 있습니다. 다음은 /etc/ssh/ssh_config 파일을 편집하는 것으로 변경할 수 있는 설정 지시자들의 예 입니다.


설정 파일을 편집하기 전에, 원래의 파일을 복사본을 만들고 쓰기에서 그것을 보호해야만 합니다. 그래서 원래의 설정을 참고로 그리고 필요한 경우 재사용할 수 있습니다.

/etc/ssh/sshd_config 파일을 복사하고 쓰기에서 그것을 보호하려면, 터미널 프롬프트에서 다음의 명령을 입력 합니다:



sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.original
sudo chmod a-w /etc/ssh/sshd_config.original

다음은 여러분이 변경할 수 있는 설정 지시자의 예 입니다:

•여러분의 OpenSSH가 기본 설정된 TCP 포트 22 대신에 TCP 포트 2222를 사용하게 하려면, 다음과 같이 Port 지시자를 변경 합니다:

Port 2222

•sshd 가 공개 키 기반의 로그인 신뢰서를 허용하게 하려면, 간단히 이 줄을 더하거나 변경 합니다:

PubkeyAuthentication yes

/etc/ssh/sshd_config 파일 내에 있고, 만약 이미 있다면, 그 줄의 주석처리 해제하는 것을 확신 하십시오.

•여러분의 OpenSSH 서버가 선-로그인 배너로 /etc/issue.net 파일의 내용을 표시하게 만드려면, 간단히 이 줄을 더하거나 변경 합니다:

Banner /etc/issue.net

/etc/ssh/sshd_config 파일 내에 있습니다.

/etc/ssh/sshd_config 파일에 변경을 만든 후에, 그 파일을 저장하고, 변경의 효과를 가지려면 다음 명령을 터미널 프롬프트에서 사용하여 sshd 서버 프로그램을 재시작 합니다:


sudo /etc/init.d/ssh restart


sshd 를 위한 많은 다른 설정 지시자는 여러분의 필요에 맞게 그 서버 프로그램의 동작을 변경하는 것을 위하여 사용가능 합니다. 그러나, 만약 여러분이 서버를 접근할 수 있는 오직 한 가지 방법이 ssh 이고, /etc/ssh/sshd_config 파일을 통해 sshd 를 설정하는데 실수를 하였다면, 서버를 재시작할 때 잠겨지거나 sshd 서버가 부정확한 설정 지시자 때문에 시작하는 것이 거부될 수 있음을 조언 합니다. 그러므로 원격 서버 상의 이 파일을 편집할 때는 정말로 조심스럽게 하시기 바랍니다.

한성컴퓨터 GTX72 Workstation


http://shopping.naver.com/detail/detail.nhn?cat_id=01140614&nv_mid=5446608066&frm=nv_model&tc=3

블로그 아카이브

개인 정보

Charlie's Angel
전체 프로필 보기