Linux Programing
Linux Programing
System calls
System calls
System call thường là một yêu cầu đến hệ điều
hành để làm một tác vụ phần cứng/chuyên biệt hệ
thống hay tác vụ đặc quyền hệ thống .
Trong Linux-1.2 có 140 system calls được định
nghĩa.
System calls như close() được hiện thực trong
Linux libc.
Việc hiện thực này xoay quanh việc gọi một là
syscall().
Các tham số truyền vào syscall() là số hiệu system
call và được theo sau bởi các tham số cần thiết
System calls
System calls
Số hiệu của system call có tìm thể thấy ở
< linux/unistd.h >
< linux/unistd.h >
Trong đó < sys/syscall.h > được cập nhật với libc mới.
Nếu có các calls xuất hiện mà chúng chưa có trong libc, ta có thể
gọi syscall().
System calls
System calls
Ví dụ, ta có thể đóng file dùng syscall() như sau
(không khuyến khích):
#include <syscall.h>
extern int syscall(int, );
int my_close(int filedescriptor)
{
return syscall(SYS_close, filedescriptor);
}
System call Parameters
System call Parameters
Trên kiến trúc i386 , system calls bị giới hạn đến
5 tham số không kể đến số hiệu system call bởi
vì việc hạn chế số lượng thanh ghi
Nếu bạn dùng Linux trên một kiến trúc khác, ta
có thể check kiểm tra <asm/unistd.h> để tìm các
syscall macros để xem phần cứng của bạn có thể
hỗ trợ đến bao nhiêu tham số
Các syscall macro này có thể được dùng thay thế
cho syscall(), nhưng nó không được khuyến khích
khi đã có những macro được phát triển thành các
hàm đầy đủ tồn tại trong thư viện lập trình
System call Parameters (tt)
System call Parameters (tt)
Do đó chỉ có những tay chuyên nghiệp mới nên
chơi với các syscall macros. Ví dụ đây là một
hàm close() có dùng syscall macro.
#include <linux/unistd.h>
#include <linux/unistd.h>
_syscall1(int, close, int,
_syscall1(int, close, int,
filedescriptor);
filedescriptor);
syscall1 macro mở rộng hàm close(). Do đó ta có
close() xuất hiện 2 lần: một trong libc và một
trong chương trình của ta. Giá trị trả về của
syscall() hay syscall macro là -1 nếu system call
thất bại và 0 hoặc lớn hơn nếu thành công.
System Call không có trong Linux
System Call không có trong Linux
Các system call sau là tồn tại trên BSD và SYS V nhưng không tồn
tại trên Linux:
audit(), auditon(), auditsvc(), fchroot(),
audit(), auditon(), auditsvc(), fchroot(),
getauid(), getdents(), getmsg(), mincore(),
getauid(), getdents(), getmsg(), mincore(),
poll(),putmsg(), setaudit(), setauid().
poll(),putmsg(), setaudit(), setauid().
The “swiss army knife” ioctl
The “swiss army knife” ioctl
phillips screwdriver, screwdriver,
screwdriver, screwdriver, corkscrew,
scissors, metal saw, wood saw, can
opener, mini screwdriver, nailfile,
metal file, wire bender, large blade,
small blade, cap lifter, wire stripper,
reamer, punch, key ring, tweezers,
multi-purpose hook, chisel, wire cutters, pin, nail cleaner,
multipurpose pliers, clef 6 pans 5mm pour connecteurs
D-SUB,embout Pozidrive 0, embout Pozidrive 1, embout
tournevis, embout Phillips 2, embout Hex (inbus), embout
Torx 8, embout Torx 10, embout Torx 15, long ballpoint
pen, toothpick
The “swiss army knife” ioctl
The “swiss army knife” ioctl
ioctl viết tắt cho input/output control và nó được dùng để thao tác
đến các character device thông qua filedescriptor.
Format của ioctl là
ioctl(unsigned int fd, unsigned int request,
ioctl(unsigned int fd, unsigned int request,
unsigned long argument)
unsigned long argument)
.
.
Giá trị trả về là -1 nếu có lỗi và =0 nếu
Giá trị trả về là -1 nếu có lỗi và =0 nếu
request thành công (tương tự như các system
request thành công (tương tự như các system
call khác).
call khác).
ioctl (tt)
ioctl (tt)
Kernel phân biệt các file đặc biệt và file thông
Kernel phân biệt các file đặc biệt và file thông
thường . File đặc biệt là những file nằm trong
thường . File đặc biệt là những file nằm trong
/dev và /proc. Chúng khác với các file thông
/dev và /proc. Chúng khác với các file thông
thường là chúng dấu các giao diện với driver và
thường là chúng dấu các giao diện với driver và
không phải là một file thật sự chứa dữ liệu text
không phải là một file thật sự chứa dữ liệu text
hay binary. Đây là triết lý của UNIX và cho phép
hay binary. Đây là triết lý của UNIX và cho phép
dùng các thao tác read/write thông thường trên
dùng các thao tác read/write thông thường trên
tất cả các file.
tất cả các file.
Bạn sẽ cần dùng ioctl nhiều khi thao tác với các
Bạn sẽ cần dùng ioctl nhiều khi thao tác với các
file đặc biệt hơn là với các file thường. Nhưng
file đặc biệt hơn là với các file thường. Nhưng
bạn cũng có thể dùng ioctl trên các file thường !
bạn cũng có thể dùng ioctl trên các file thường !
I
I
nter
nter
P
P
rocess
rocess
C
C
ommunications-IPC
ommunications-IPC
Cơ chế IPC trong Linux cung cấp một phương pháp
cho nhiều tiến trình giao tiếp với nhau. Có nhiều
phương pháp IPC cho Linux C programmers áp dụng:
Half-duplex UNIX pipes
FIFOs (named pipes)
SYSV style message queues
SYSV style semaphore sets
SYSV style shared memory segments
Networking sockets (Berkeley style, không đề cập )
Full-duplex pipes (STREAMS pipes,, không đề cập )
Các phương pháp này, khi được dùng một cách hiệu
quả, sẽ mang lại một framework vững chắc cho
client/server development trên bất kỳ một hệ UNIX
nào (bao gồm cả Linux).
Half-duplex UNIX Pipes
Half-duplex UNIX Pipes
Khái niệm căn bản:
pipe
là một phương pháp
của
việc kết nối
standard output
của một tiến trình
process vào một
standard input
của tiến trình
khác. Pipes là một công cụ IPC cổ nhất, nó có từ
giai đoạn phôi thai nhất của HĐH UNIX. Chúng
cung cấp một của việc giao tiêế 1 chiều (thuật
ngữ half-duplex) giữa các process. Tính năng
này được dùng rộng rãi, thậm chí trong shell của
Unix
ls | sort | lp
Half-duplex UNIX Pipes
Half-duplex UNIX Pipes
Tạo pipes trong C
Để tạo một pipe đơn giản trong C, ta cần
Để tạo một pipe đơn giản trong C, ta cần
dùng pipe() system call. Nó cần 1 tham số, đó
dùng pipe() system call. Nó cần 1 tham số, đó
là một array có 2 số integer và nếu thành
là một array có 2 số integer và nếu thành
công, array sẽ chứa 2 file descriptors mới để
công, array sẽ chứa 2 file descriptors mới để
được dùng cho pipeline. Sau khi tạo pipe,
được dùng cho pipeline. Sau khi tạo pipe,
process thường sinh ra 1 tiến trình mới (lưu ý
process thường sinh ra 1 tiến trình mới (lưu ý
rằng, tiến trình con thừa kế mô tả file đã mở).
rằng, tiến trình con thừa kế mô tả file đã mở).
T
T
ạo
ạo
pipes trong C
pipes trong C
SYSTEM CALL: pipe();
PROTOTYPE: int pipe( int fd[2] );
RETURNS: 0 on success
-1 on error: errno = EMFILE (no free
descriptors)
EMFILE (system file table is full)
EFAULT (fd array is not valid)
NOTES: fd[0] is set up for reading, fd[1] is set
up for writing
T
T
ạo
ạo
pipes trong C
pipes trong C
Giá trị integer đầu tiên trong array được thiết lập
và mở ra cho việc đọc, trong khi số integer thứ hai
được thiết lập và mở ra cho việc ghi.
Nói một cách hình tượng, output của fd1 trở thành
input cho fd0. Một lần nữa, tất cả data di chuyển
thông qua pipe đi vào kernel.
#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
main()
main()
{
{
int fd[2];
int fd[2];
pipe(fd);
pipe(fd);
…
…
}
}
Ví dụ
Ví dụ
/***MODULE: pipe.c***/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int fd[2], nbytes;
pid_t childpid;
char string[] = "Hello, world!\n";
char readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1){
perror("fork");
exit(1);
}
if(childpid == 0) {
/* Child process closes up input side of
pipe */
close(fd[0]);
/* Send "string" through the output side
/* Send "string" through the output side
of pipe */
of pipe */
write(fd[1], string, strlen(string));
write(fd[1], string, strlen(string));
exit(0);
exit(0);
}
}
else {
else {
/* Parent process closes up output side
/* Parent process closes up output side
of pipe */
of pipe */
close(fd[1]);
close(fd[1]);
/* Read in a string from the pipe */
/* Read in a string from the pipe */
nbytes = read(fd[0], readbuffer,
nbytes = read(fd[0], readbuffer,
sizeof(readbuffer));
sizeof(readbuffer));
printf("Received string: %s",
printf("Received string: %s",
readbuffer); }
readbuffer); }
return(0);
return(0);
}
}
Các lưu ý trong
half-duplex pipes
pipes hai chiều có thể được tạo ra bằng cách mở hai
pipes, và được gán lại file descriptors một cách
thích hợp trong tiến trình con .
Lời gọi pipe() phải được thực hiện trước khi gọi
fork(), nếu không thì descriptors sẽ không được
thừa kế bởi tiến trình con !
Với half-duplex pipes, bất kỳ tiến trình kết nối nào
phải được chia sẽ cùng một tổ tiên. Khi pipe cư trú
bên trong sự kềm hãm của kernel, bất cứ một tiến
trình nào mà không cùng tổ tiên của pipe thì không
có cách nào tìm đến được nó.
Named Pipes (FIFOs)
Named Pipes (FIFOs)
Khái niệm: named pipe hoạt động giống như pipe thông thường,
nhưng có một số khác biệt đáng chú ý:
Named pipes tồn tại như là một device special
Named pipes tồn tại như là một device special
file trong file system.
file trong file system.
Các tiến trình khác tổ tiên có thể chia sẽ data
Các tiến trình khác tổ tiên có thể chia sẽ data
thông qua một named pipe.
thông qua một named pipe.
Khi tất cả I/O được hoàn tất bởi các tiến trình
Khi tất cả I/O được hoàn tất bởi các tiến trình
chia sẻ, named pipe còn lưu lại trong file
chia sẻ, named pipe còn lưu lại trong file
system để dùng sau.
system để dùng sau.
Tạo FIFO
Tạo FIFO
Có nhiều cách để tạo một named pipe. Hai cách đầu tiên là tạo trực
tiếp từ shell.
mknod MYFIFO p
mknod MYFIFO p
mkfifo a=rw MYFIFO
mkfifo a=rw MYFIFO
FIFO files có thể được xác định trong physical file system bởi ký tự “p”
ở đầu tiên:
[root@pascal root]# ls -l MYFIFO
[root@pascal root]# ls -l MYFIFO
prw-r r 1 root root 0 Aug 23 23:35 MYFIFO
prw-r r 1 root root 0 Aug 23 23:35 MYFIFO
[root@pascal root]#
[root@pascal root]#
Tạo FIFO trong C
Tạo FIFO trong C
Để tạo FIFO trong C, ta có thể dùng mknod() system call:
LIBRARY FUNCTION: mknod();
LIBRARY FUNCTION: mknod();
PROTOTYPE: int mknod( char *pathname, mode_t mode, dev_t dev);
PROTOTYPE: int mknod( char *pathname, mode_t mode, dev_t dev);
RETURNS: 0 on success,
RETURNS: 0 on success,
-1 on error:errno=EFAULT (pathname invalid)
-1 on error:errno=EFAULT (pathname invalid)
NOTES: Tạo filesystem node (file, device file, or FIFO)
EACCES (permission denied)
EACCES (permission denied)
ENAMETOOLONG (pathname
ENAMETOOLONG (pathname
too long)
too long)
ENOENT (invalid pathname)
ENOENT (invalid pathname)
ENOTDIR (invalid pathname)
ENOTDIR (invalid pathname)
mknod
mknod
Muốn biến rõ hơn về mknod thì hãy dùng lệnh
man,nhưng xét ví dụ đơn giản sau trong C:
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
Ở đây, file “/tmp/MYFIFO” được tạo ra như là một
FIFO file. Quyền của file là “0666”, mặc dù chúng bị
ảnh hưởng bởi umask setting như sau:
final_umask = requested_permissions & ˜original_umask
final_umask = requested_permissions & ˜original_umask
Một mẹo nhỏ dùng umask() system call để tạm thời
vô hiệu giá trị umask :
umask(0);
umask(0);
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
Thao tác FIFO
Thao tác FIFO
Các thao tác I/O trên FIFO là cần thiết như đối
với các pipe thông thường, với một ngoại lệ
chính yếu. Một “open” system call hay hàm thư
viện cần phải được dùng để physically open up
một kênh tới pipe. Với half-duplex pipes, điều
này là không cần thiết, bởi vì khi đó pipe nằm
trong kernel và không phải trên physical
filesystem. Trong ví dụ, chúng ta sẽ xem như
pipe là một stream, mở nó ra với fopen() và
đóng với fclose().
fifoserver.c
fifoserver.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/stat.h>
#define FIFO_FILE "MYFIFO"
int main(void)
{
FILE *fp;
char readbuf[80];
/* Create the FIFO if it does not exist */
umask(0);
mknod(FIFO_FILE, S_IFIFO|0666, 0);
fifoserver.c (tt)
fifoserver.c (tt)
while(1)
{
fp = fopen(FIFO_FILE, "r");
fgets(readbuf, 80, fp);
printf("Received string: %s\n", readbuf);
fclose(fp);
}
return(0);
}
fifoclient.c
fifoclient.c
#include <stdio.h>
#include <stdlib.h>
#define FIFO_FILE "MYFIFO"
int main(int argc, char *argv[])
{
FILE *fp;
if ( argc != 2 ) {
printf("USAGE: fifoclient [string]\n");
exit(1);
}